# saif-application-permissions

This Terraform module manages permissions and roles for an existing Azure Entra ID (Azure Active Directory) application. It is designed to work with applications named using the pattern `{project-id}-{environment-short-name}`.

## ✨ Features

- 📋 **API Permissions Management**: Configure Microsoft Graph and other API permissions
- 🎯 **Scopes**: Define OAuth2 delegated scopes exposed by your application
- 🔑 **App Roles**: Create and manage application roles for RBAC
- 👥 **Role Assignments**: Assign app roles to users, groups, or service principals
- ✅ **Admin Consent**: Automatically grants admin consent for API permissions
- 🔓 **Pre-Authorization**: Pre-authorize client applications to access your application's scopes

### ⚠️ Important Notes

- This module is for **existing applications only** - it does not create new applications
- Default configurations (user_impersonation scope, optional claims, self-authorization) are **not** applied by this module
- For new applications with default configurations, use the `application` module instead
- Optional claims management is disabled (`configure_optional_claims=false`) to prevent conflicts with applications created by the `application` module

## 📋 Prerequisites

- An existing Azure AD application (created separately)
- Appropriate permissions to manage the application
- Application name following the pattern: `{project-id}-{environment-short-name}`

## 🚀 Usage

### Basic Example

```hcl
module "app_permissions" {
  source = "../saif-application-permissions"

  project_id             = "myproject"
  environment_short_name = "dev"

  # Application permissions (app-only, no user context)
  api_roles = [
    {
      api_name        = "Microsoft Graph"
      permission_name = "User.Read.All"
    },
    {
      api_name        = "Microsoft Graph"
      permission_name = "Group.Read.All"
    }
  ]

  # Delegated permissions (requires user context)
  api_scopes = [
    {
      api_name        = "Microsoft Graph"
      permission_name = "User.Read"
    }
  ]

  grant_admin_consent = true
}
```

### Advanced Example - Full Configuration with Cross-Application References

```hcl
module "app_permissions" {
  source = "../saif-application-permissions"

  project_id             = "myproject"
  environment            = "production"
  environment_short_name = "prod"

  # Application permissions (app-only, no user context)
  api_roles = [
    {
      api_name        = "Microsoft Graph"
      permission_name = "User.Read.All"
    }
  ]

  grant_admin_consent = true

  # scopes (exposing your API) - UUIDs auto-generated!
  scopes = [
    {
      value                      = "Data.Read"  # UUID auto-generated from value
      admin_consent_description  = "Allow the application to read data"
      admin_consent_display_name = "Read data"
      user_consent_description   = "Allow the application to read your data"
      user_consent_display_name  = "Read your data"
      type                       = "User"
    }
  ]

  # App roles (for RBAC) - UUIDs auto-generated!
  app_roles = [
    {
      value                = "Admin"  # UUID auto-generated from value
      allowed_member_types = ["User", "Application"]
      description          = "Administrators have full access"
      display_name         = "Administrator"
    }
  ]

  # App role assignments - Use role values!
  app_role_assignments = [
    {
      role_value          = "Admin"  # Reference role by value
      principal_object_id = "c3d4e5f6-a7b8-9012-cdef-123456789012"
    }
  ]

  # Pre-authorized applications - Cross-application references made easy!
  pre_authorized_applications = [
    {
      project_id   = "clientapp" # Automatically looks up 'clientapp-prod'
      scope_values = ["Data.Read"]
    },
    {
      project_id   = "anotherapp"
      scope_values = ["Data.Read", "Data.Write"]
    }
  ]
}
```

### Example - Exposing an API with Scopes

```hcl
module "api_scopes" {
  source = "../saif-application-permissions"

  project_id             = "myapi"
  environment_short_name = "dev"

  # Define scopes that client applications can request (UUIDs auto-generated!)
  scopes = [
    {
      value                      = "Orders.Read"  # UUID auto-generated from value
      admin_consent_description  = "Allow the application to read orders"
      admin_consent_display_name = "Read orders"
      user_consent_description   = "Allow the application to read your orders"
      user_consent_display_name  = "Read your orders"
      type                       = "User"
    },
    {
      value                      = "Orders.Write"  # UUID auto-generated from value
      admin_consent_description  = "Allow the application to write orders"
      admin_consent_display_name = "Write orders"
      type                       = "Admin"  # Requires admin consent
    }
  ]
}
```

### Example - Application Roles for RBAC

```hcl
module "app_rbac" {
  source = "../saif-application-permissions"

  project_id             = "myapp"
  environment_short_name = "prod"

  # Define roles for your application (UUIDs auto-generated!)
  app_roles = [
    {
      value                = "Reader"  # UUID auto-generated from value
      allowed_member_types = ["User"]
      description          = "Can read data"
      display_name         = "Data Reader"
    },
    {
      value                = "Writer"  # UUID auto-generated from value
      allowed_member_types = ["User"]
      description          = "Can read and write data"
      display_name         = "Data Writer"
    },
    {
      value                = "ServiceAccount"  # UUID auto-generated from value
      allowed_member_types = ["Application"]
      description          = "Service account access"
      display_name         = "Service Account"
    }
  ]

  # Assign roles to users/service principals
  app_role_assignments = [
    {
      role_value    = "Reader"
      business_role = "DataReader"
    },
    {
      role_value    = "Writer"
      business_role = "DataWriter"
    }
  ]
}
```

## 📊 Inputs

| Name                        | Description                                                                                           | Type           | Default | Required |
| --------------------------- | ----------------------------------------------------------------------------------------------------- | -------------- | ------- | :------: |
| project_id                  | The project identifier (e.g., 'myproject'). Used to construct '{project_id}-{environment_short_name}' | `string`       | n/a     |    ✅     |
| environment                 | The full environment name (e.g., 'development', 'production'). Used for tagging and documentation.    | `string`       | n/a     |    ✅     |
| environment_short_name      | The short environment name (e.g., 'dev', 'prod') to construct application name                        | `string`       | n/a     |    ✅     |
| application                 | Optional application identity object `{ id, client_id }` (e.g. `{ id = module.saif-appservices.application_id, client_id = module.saif-appservices.application_client_id }`). Skips the display-name lookup and avoids needing a coarse `depends_on` on the module that created the app. Validated to reject null/empty fields. | `object({ id = string, client_id = string })` | `null`  |    ❌     |
| service_principal           | Optional service principal identity object `{ object_id, client_id }` (e.g. `{ object_id = module.saif-appservices.application_principal_object_id, client_id = module.saif-appservices.application_principal_client_id }`). Skips the service-principal lookup. Validated to reject null/empty fields. | `object({ object_id = string, client_id = string })` | `null`  |    ❌     |
| api_roles                   | List of API application permissions (Role type) - app-only, no user context required                  | `list(object)` | `[]`    |    ❌     |
| api_scopes                  | List of API delegated permissions (Scope type) - requires user context                                | `list(object)` | `[]`    |    ❌     |
| grant_admin_consent         | Whether to grant admin consent for API permissions (applies to api_roles)                             | `bool`         | `true`  |    ❌     |
| scopes                      | List of OAuth2 scopes to expose from this application                                                 | `list(object)` | `[]`    |    ❌     |
| app_roles                   | List of app roles to define for this application                                                      | `list(object)` | `[]`    |    ❌     |
| app_role_assignments        | List of app role assignments to users, groups, or service principals                                  | `list(object)` | `[]`    |    ❌     |
| pre_authorized_applications | List of applications to pre-authorize. Reference by 'project_id'                                      | `list(object)` | `[]`    |    ❌     |

## 📤 Outputs

| Name                        | Description                                                    |
| --------------------------- | -------------------------------------------------------------- |
| application_object_id       | The object ID of the Azure AD application                      |
| application_id              | The application ID of the Azure AD application                 |
| client_id                   | The client ID of the Azure AD application                      |
| service_principal_object_id | The object ID of the service principal                         |
| api_permissions             | List of API permissions configured                             |
| scopes                      | List of OAuth2 scopes exposed                                  |
| app_roles                   | List of app roles defined                                      |
| app_role_assignments        | Map of app role assignments                                    |
| pre_authorized_applications | List of pre-authorized applications                            |
| granted_permissions         | Map of granted API permissions (when admin consent is granted) |
| summary                     | Summary of application permissions configuration               |

## 💡 Tips

### 🎯 Default Configurations

This module automatically configures several critical settings for all Entra ID applications:

1. **🔐 user_impersonation Scope**: Every application automatically exposes a `user_impersonation` scope that allows client applications to access the API on behalf of a signed-in user. This scope:
   - Is added by default (no configuration needed)
   - Uses type `"User"` (users can consent)
   - Follows Azure naming conventions
   - Is automatically pre-authorized for the application itself

2. **👥 Groups Claim Configuration**: Optional claims are configured to include groups in tokens with the following properties:
   - Groups are included in access tokens, ID tokens, and SAML tokens
   - Groups include `sam_account_name` for on-premises Active Directory groups
   - Groups are emitted as roles for easier claim processing
   - This allows your application to check group membership using the `groups` claim

   **🔑 Token Type Claim**: The `idtyp` claim is included in access and ID tokens to indicate whether the token is for an application (`app`) or a user (`user`), making it easy to distinguish between service-to-service and user-delegated authentication.

3. **✅ Self-Authorization**: The application automatically grants admin consent to itself for the `user_impersonation` scope, simplifying the consent flow for common scenarios.

### ⚠️ Important Notes

1. **✨ Auto-Generated UUIDs**: Scope IDs and role IDs are automatically generated from their values - no manual UUID management!
2. **🎯 Platform-First Design**: Use `project_id` and `environment_short_name` to reference applications - no need for UUIDs!
3. **📝 Intuitive API Permissions**:
   - Use `api_roles` for **application permissions** (app-to-app, no user context)
   - Use `api_scopes` for **delegated permissions** (requires user signin)
   - No need to specify `type` - the variable name makes it clear!
4. **🔗 Cross-Application References**: Reference other applications by `project_id` - automatically looks up `{project_id}-{environment_short_name}`:

   ```hcl
   pre_authorized_applications = [
     {
       project_id   = "otherapp"  # Automatically looks up 'otherapp-{env}'
       scope_values = ["Data.Read"]
     }
   ]
   ```

5. **📋 Admin Consent**: Defaults to `true` since application permissions require consent to be functional
6. **👥 Role Assignments**: Assign roles using business role names - automatically maps to `bus-role.{name}` (prod) or `bus-role-np.{name}` (non-prod) groups
7. **🔄 Scope Types**:
   - **User**: Users can consent themselves
   - **Admin**: Requires admin consent

## 🏗️ Architecture

This module manages permissions for an existing application by:

1. Looking up the application by display name
2. Configuring API permissions the application needs (consuming APIs)
3. Defining scopes the application exposes (for other apps to consume)
4. Creating app roles for RBAC within your application
5. Assigning roles to users/groups/service principals
6. Pre-authorizing trusted client applications

## 📚 Related Documentation

- [Azure AD Application Permissions](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent)
- [OAuth2 scopes](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow)
- [Application Roles](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps)
- [Pre-authorized Applications](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#pre-authorize-applications)

## 🔗 Resources

- `azuread_application_registration` (data source)
- `azuread_service_principal` (data source)
- `azuread_application_api_access`
- `azuread_app_role_assignment`
- `azuread_application_permission_scope`
- `azuread_application_app_role`
- `azuread_application_pre_authorized`

---

**Note**: This module is part of the SAIF Platform Terraform modules. Follow the `terraform` skill in `.github/skills/terraform/` for development guidelines.

<!-- BEGIN_TF_DOCS -->


## Features

- 📋 **API Permissions Management**: Configure Microsoft Graph and other API permissions
- 🎯 **scopes**: Define OAuth2 delegated scopes exposed by your application
- 🔑 **App Roles**: Create and manage application roles for RBAC
- 👥 **Role Assignments**: Assign app roles to users, groups, or service principals
- ✅ **Admin Consent**: Optionally grant admin consent for API permissions
- 🔓 **Pre-Authorization**: Pre-authorize client applications to access your application's scopes

## Usage

### Basic Example

```hcl
module "app_permissions" {
  source = "../saif-application-permissions"

  application_name = "myproject-dev"
  
  api_permissions = [
    {
      api_name        = "Microsoft Graph"
      permission_name = "User.Read.All"
      type            = "Role"
    }
  ]
  
  grant_admin_consent = true
}
```

## Requirements

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0.0, < 2.0.0 |
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | >= 3.0, < 4.0 |

## Providers

## Providers

| Name | Version |
|------|---------|
| <a name="provider_azuread"></a> [azuread](#provider\_azuread) | 3.9.0 |

## Inputs

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_app_role_assignments"></a> [app\_role\_assignments](#input\_app\_role\_assignments) | List of app role assignments to business role groups. Groups are looked up by business role name and environment. | <pre>list(object({<br/>    role_value    = string # Value of the app role to assign (e.g., "Admin")<br/>    business_role = string # Business role name (e.g., "TechSupport") - will be converted to group display name<br/>  }))</pre> | `[]` | no |
| <a name="input_app_roles"></a> [app\_roles](#input\_app\_roles) | App roles that this application exposes to other applications or users | <pre>list(object({<br/>    value                = string       # The role value (e.g., "Admin") - UUID will be auto-generated<br/>    allowed_member_types = list(string) # ["User"], ["Application"], or both<br/>    description          = string       # Description of the role<br/>    display_name         = string       # Display name of the role<br/>  }))</pre> | `[]` | no |
| <a name="input_application"></a> [application](#input\_application) | Optional application identity to pass in directly instead of looking up the application by display name. When set, both `id` (the application object ID) and `client_id` must be provided together, e.g. `{ id = module.saif-appservices.application_id, client_id = module.saif-appservices.application_client_id }`. Passing a single object (instead of separate id/client\_id/boolean inputs) lets `count` safely branch on `var.application == null` even when the id/client\_id values themselves are unknown at plan time (e.g. because the source module's application resource is being created or replaced) — an object literal with unknown attribute values is still a known, non-null object, so there's no risk of an 'Invalid count argument' error. Leave null to look the application up by '{project\_id}-{environment\_short\_name}'. | <pre>object({<br/>    id        = string<br/>    client_id = string<br/>  })</pre> | `null` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | The full environment name (e.g., 'development', 'production'). Used for tagging and documentation purposes. | `string` | n/a | yes |
| <a name="input_environment_short_name"></a> [environment\_short\_name](#input\_environment\_short\_name) | The short environment name used to construct the application name as '{project\_id}-{environment\_short\_name}' (e.g., 'dev', 'prod'). | `string` | n/a | yes |
| <a name="input_pre_authorized_applications"></a> [pre\_authorized\_applications](#input\_pre\_authorized\_applications) | List of applications to pre-authorize for accessing this application's scopes and/or app roles. Reference applications by their project\_id. | <pre>list(object({<br/>    project_id      = string                     # Project ID to look up application as '{project_id}-{environment_short_name}'<br/>    scope_values    = list(string)               # List of scope values (not UUIDs) to authorize (e.g., ["Data.Read", "Data.Write"])<br/>    app_role_values = optional(list(string), []) # List of app role values (application permissions) for service-to-service calls (e.g., ["App.Read"])<br/>  }))</pre> | `[]` | no |
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | The project identifier for the application (e.g., 'myproject'). Used to construct the application name as '{project\_id}-{environment\_short\_name}'. | `string` | n/a | yes |
| <a name="input_requested_application_permissions"></a> [requested\_application\_permissions](#input\_requested\_application\_permissions) | Application permissions (Role type) to request from other APIs like Microsoft Graph. These are app-only permissions that don't require user context. | <pre>list(object({<br/>    api_name        = string # e.g., "Microsoft Graph"<br/>    permission_name = string # e.g., "User.Read.All"<br/>  }))</pre> | `[]` | no |
| <a name="input_requested_delegated_permissions"></a> [requested\_delegated\_permissions](#input\_requested\_delegated\_permissions) | Delegated permissions (Scope type) to request from other APIs like Microsoft Graph. These permissions require user context. | <pre>list(object({<br/>    api_name        = string # e.g., "Microsoft Graph"<br/>    permission_name = string # e.g., "User.Read"<br/>  }))</pre> | `[]` | no |
| <a name="input_scopes"></a> [scopes](#input\_scopes) | OAuth2 scopes that this application exposes to other applications | <pre>list(object({<br/>    value                      = string                    # The scope value (e.g., "Data.Read") - UUID will be auto-generated<br/>    admin_consent_description  = string                    # Description shown to admins<br/>    admin_consent_display_name = string                    # Display name shown to admins<br/>    user_consent_description   = optional(string, "")      # Description shown to users<br/>    user_consent_display_name  = optional(string, "")      # Display name shown to users<br/>    type                       = optional(string, "Admin") # "Admin" or "User" - defaults to "Admin" for automatic consent<br/>  }))</pre> | `[]` | no |
| <a name="input_service_principal"></a> [service\_principal](#input\_service\_principal) | Optional service principal identity to pass in directly instead of looking it up via the application's client ID. When set, both `object_id` and `client_id` must be provided together, e.g. `{ object_id = module.saif-appservices.application_principal_object_id, client_id = module.saif-appservices.application_principal_client_id }`. See `application` for why a single object is used instead of separate id/client\_id/boolean inputs. Leave null to look the service principal up automatically. | <pre>object({<br/>    object_id = string<br/>    client_id = string<br/>  })</pre> | `null` | no |

## Outputs

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_api_permissions"></a> [api\_permissions](#output\_api\_permissions) | List of API permissions configured for the application |
| <a name="output_app_role_assignments"></a> [app\_role\_assignments](#output\_app\_role\_assignments) | Map of app role assignments |
| <a name="output_app_roles"></a> [app\_roles](#output\_app\_roles) | List of app roles defined for the application |
| <a name="output_application_id"></a> [application\_id](#output\_application\_id) | The application ID (client ID) of the Azure AD application |
| <a name="output_application_object_id"></a> [application\_object\_id](#output\_application\_object\_id) | The object ID of the Azure AD application |
| <a name="output_client_id"></a> [client\_id](#output\_client\_id) | The client ID of the Azure AD application |
| <a name="output_granted_permissions"></a> [granted\_permissions](#output\_granted\_permissions) | Map of granted API permissions with their details (admin consent granted) |
| <a name="output_pre_authorized_applications"></a> [pre\_authorized\_applications](#output\_pre\_authorized\_applications) | List of pre-authorized applications |
| <a name="output_scopes"></a> [scopes](#output\_scopes) | List of OAuth2 scopes the application |
| <a name="output_service_principal_object_id"></a> [service\_principal\_object\_id](#output\_service\_principal\_object\_id) | The object ID of the service principal |
| <a name="output_summary"></a> [summary](#output\_summary) | Summary of application permissions configuration |

## Resources

## Resources

| Name | Type |
|------|------|
| [azuread_application.application](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/application) | data source |
| [azuread_group.role_assignments](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/group) | data source |
| [azuread_service_principal.application_sp](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/service_principal) | data source |

<!-- END_TF_DOCS -->

---

[View source on GitHub](https://github.com/saif-corp/forge/blob/main/src/terraform/saif-application-permissions/README.md)
