Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OAuth scope handling #282

Merged
merged 4 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/resources/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ resource "fusionauth_application" "Forum" {
- `id_token_populate_id` - (Optional) The Id of the Lambda that will be invoked when an Id token is generated for this application during an OpenID Connect authentication request.
- `samlv2_populate_id` - (Optional) The Id of the Lambda that will be invoked when a a SAML response is generated during a SAML authentication request.
- `self_service_registration_validation_id` - (Optional) The unique Id of the lambda that will be used to perform additional validation on registration form steps.
- `userinfo_populate_id` - (Optional) The Id of the Lambda that will be invoked when a UserInfo response is generated for this application.
* `login_configuration` - (Optional)
- `allow_token_refresh` - (Optional) Indicates if a JWT may be refreshed using a Refresh Token for this application. This configuration is separate from issuing new Refresh Tokens which is controlled by the generateRefreshTokens parameter. This configuration indicates specifically if an existing Refresh Token may be used to request a new JWT using the Refresh API.
- `generate_refresh_tokens` - (Optional) Indicates if a Refresh Token should be issued from the Login API
Expand All @@ -156,13 +157,20 @@ resource "fusionauth_application" "Forum" {
- `authorized_url_validation_policy` - (Optional) Determines whether wildcard expressions will be allowed in the authorized_redirect_urls and authorized_origin_urls.
- `client_secret` - (Optional) The OAuth 2.0 client secret. If you leave this blank during a POST, a secure secret will be generated for you. If you leave this blank during PUT, the previous value will be maintained. For both POST and PUT you can provide a value and it will be stored.
- `client_authentication_policy` - (Optional) Determines the client authentication requirements for the OAuth 2.0 Token endpoint.
- `consent_mode` (Optional) Controls the policy for prompting a user to consent to requested OAuth scopes. This configuration only takes effect when `application.oauthConfiguration.relationship` is `ThirdParty`. The possible values are:
- `AlwaysPrompt` - Always prompt the user for consent.
- `RememberDecision` - Remember previous consents; only prompt if the choice expires or if the requested or required scopes have changed. The duration of this persisted choice is controlled by the Tenant’s `externalIdentifierConfiguration.rememberOAuthScopeConsentChoiceTimeToLiveInSeconds` value.
- `NeverPrompt` - The user will be never be prompted to consent to requested OAuth scopes. Permission will be granted implicitly as if this were a `FirstParty` application. This configuration is meant for testing purposes only and should not be used in production.
- `debug` - (Optional) Whether or not FusionAuth will log a debug Event Log. This is particular useful for debugging the authorization code exchange with the Token endpoint during an Authorization Code grant."
- `device_verification_url` - (Optional) The device verification URL to be used with the Device Code grant type, this field is required when device_code is enabled.
- `enabled_grants` - (Optional) The enabled grants for this application. In order to utilize a particular grant with the OAuth 2.0 endpoints you must have enabled the grant.
- `generate_refresh_tokens` - (Optional) Determines if the OAuth 2.0 Token endpoint will generate a refresh token when the offline_access scope is requested.
- `logout_behavior` - (Optional) Behavior when /oauth2/logout is called.
- `logout_url` - (Optional) The logout URL for the Application. FusionAuth will redirect to this URL after the user logs out of OAuth.
- `proof_key_for_code_exchange_policy` - (Optional) Determines the PKCE requirements when using the authorization code grant.
- `relationship` (Optional) The application’s relationship to the OAuth server. The possible values are:
- `FirstParty` - The application has the same owner as the authorization server. Consent to requested OAuth scopes is granted implicitly.
- `ThirdParty` - The application is external to the authorization server. Users will be prompted to consent to requested OAuth scopes based on the application object’s `oauthConfiguration.consentMode` value. Note: An Essentials or Enterprise plan is required to utilize third-party applications.
- `require_client_authentication` - (Optional) Determines if the OAuth 2.0 Token endpoint requires client authentication. If this is enabled, the client must provide client credentials when using the Token endpoint. The client_id and client_secret may be provided using a Basic Authorization HTTP header, or by sending these parameters in the request body using POST data.
- `require_registration` - (Optional) When enabled the user will be required to be registered, or complete registration before redirecting to the configured callback in the authorization code grant or the implicit grant. This configuration does not currently apply to any other grant.
- `provided_scope_policy` - (Optional) Configures which of the default scopes are enabled and required.
Expand All @@ -178,6 +186,13 @@ resource "fusionauth_application" "Forum" {
* `profile`
* `enabled` - (Optional)
* `required` - (Optional)
- `unknown_scope_policy` Controls the policy for handling unknown scopes on an OAuth request. The possible values are:
- `Allow` - Unknown scopes will be allowed on the request, passed through the OAuth workflow, and written to the resulting tokens without consent.
- `Remove` - Unknown scopes will be removed from the OAuth workflow, but the workflow will proceed without them.
- `Reject` - Unknown scopes will be rejected and cause the OAuth workflow to fail with an error.
- `scope_handling_policy` Controls the policy for handling of OAuth scopes when populating JWTs and the UserInfo response. The possible values are:
- `Compatibility` - OAuth workflows will populate JWT and UserInfo claims in a manner compatible with versions of FusionAuth before version 1.50.0.
- `Strict` - OAuth workflows will populate token and UserInfo claims according to the OpenID Connect 1.0 specification based on requested and consented scopes.
* `registration_configuration` - (Optional)
- `birth_date` - (Optional)
* `enabled` - (Optional)
Expand Down
56 changes: 51 additions & 5 deletions fusionauth/resource_fusionauth_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ func newApplication() *schema.Resource {
Optional: true,
Description: "The unique Id of the lambda that will be used to perform additional validation on registration form steps.",
},
"userinfo_populate_id": {
Type: schema.TypeString,
Optional: true,
Description: "The Id of the Lambda that will be invoked when a UserInfo response is generated for this application.",
},
},
},
},
Expand Down Expand Up @@ -625,6 +630,17 @@ func newOAuthConfiguration() *schema.Resource {
Description: "The OAuth 2.0 client id. If you leave this blank during a POST, a client id will be generated for you. If you leave this blank during PUT, the previous value will be maintained. For both POST and PUT you can provide a value and it will be stored.",
Computed: true,
},
"consent_mode": {
Type: schema.TypeString,
Optional: true,
Default: fusionauth.OAuthScopeConsentMode_AlwaysPrompt.String(),
Description: "Controls the policy for prompting a user to consent to requested OAuth scopes. This configuration only takes effect when `application.oauthConfiguration.relationship` is `ThirdParty`. The possible values are: `AlwaysPrompt` - Always prompt the user for consent. `RememberDecision` - Remember previous consents; only prompt if the choice expires or if the requested or required scopes have changed. The duration of this persisted choice is controlled by the Tenant’s `externalIdentifierConfiguration.rememberOAuthScopeConsentChoiceTimeToLiveInSeconds` value. `NeverPrompt` - The user will be never be prompted to consent to requested OAuth scopes. Permission will be granted implicitly as if this were a `FirstParty` application. This configuration is meant for testing purposes only and should not be used in production.",
ValidateFunc: validation.StringInSlice([]string{
fusionauth.OAuthScopeConsentMode_AlwaysPrompt.String(),
fusionauth.OAuthScopeConsentMode_RememberDecision.String(),
fusionauth.OAuthScopeConsentMode_NeverPrompt.String(),
}, false),
},
"debug": {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -673,6 +689,22 @@ func newOAuthConfiguration() *schema.Resource {
}, false),
Description: "Determines the PKCE requirements when using the authorization code grant.",
},
"provided_scope_policy": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: newOAuthConfigurationProvidedScopePolicy(),
},
"relationship": {
Type: schema.TypeString,
Optional: true,
Default: fusionauth.OAuthApplicationRelationship_FirstParty.String(),
Description: "The application’s relationship to the OAuth server. The possible values are: `FirstParty` - The application has the same owner as the authorization server. Consent to requested OAuth scopes is granted implicitly. `ThirdParty` - The application is external to the authorization server. Users will be prompted to consent to requested OAuth scopes based on the application object’s `oauthConfiguration.consentMode` value. Note: An Essentials or Enterprise plan is required to utilize third-party applications.",
ValidateFunc: validation.StringInSlice([]string{
fusionauth.OAuthApplicationRelationship_FirstParty.String(),
fusionauth.OAuthApplicationRelationship_ThirdParty.String(),
}, false),
},
"require_client_authentication": {
Type: schema.TypeBool,
Optional: true,
Expand All @@ -686,10 +718,24 @@ func newOAuthConfiguration() *schema.Resource {
Default: false,
Description: "When enabled the user will be required to be registered, or complete registration before redirecting to the configured callback in the authorization code grant or the implicit grant. This configuration does not currently apply to any other grant.",
},
"provided_scope_policy": {
Type: schema.TypeList,
Optional: true,
Elem: newOAuthConfigurationScopePolicy(),
"scope_handling_policy": {
Type: schema.TypeString,
Required: true,
Description: "Controls the policy for handling of OAuth scopes when populating JWTs and the UserInfo response. The possible values are: `Compatibility` - OAuth workflows will populate JWT and UserInfo claims in a manner compatible with versions of FusionAuth before version 1.50.0. `Strict` - OAuth workflows will populate token and UserInfo claims according to the OpenID Connect 1.0 specification based on requested and consented scopes.",
ValidateFunc: validation.StringInSlice([]string{
fusionauth.OAuthScopeHandlingPolicy_Compatibility.String(),
fusionauth.OAuthScopeHandlingPolicy_Strict.String(),
}, false),
},
"unknown_scope_policy": {
Type: schema.TypeString,
Required: true,
Description: "Controls the policy for handling unknown scopes on an OAuth request. The possible values are: `Allow` - Unknown scopes will be allowed on the request, passed through the OAuth workflow, and written to the resulting tokens without consent. `Remove` - Unknown scopes will be removed from the OAuth workflow, but the workflow will proceed without them. `Reject` - Unknown scopes will be rejected and cause the OAuth workflow to fail with an error.",
ValidateFunc: validation.StringInSlice([]string{
fusionauth.UnknownScopePolicy_Allow.String(),
fusionauth.UnknownScopePolicy_Remove.String(),
fusionauth.UnknownScopePolicy_Reject.String(),
}, false),
},
},
}
Expand Down Expand Up @@ -860,7 +906,7 @@ func newRegistrationConfiguration() *schema.Resource {
}
}

func newOAuthConfigurationScopePolicy() *schema.Resource {
func newOAuthConfigurationProvidedScopePolicy() *schema.Resource {
requireable := func() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
Expand Down
54 changes: 46 additions & 8 deletions fusionauth/resource_fusionauth_application_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func buildApplication(data *schema.ResourceData) fusionauth.Application {
IdTokenPopulateId: data.Get("lambda_configuration.0.id_token_populate_id").(string),
Samlv2PopulateId: data.Get("lambda_configuration.0.samlv2_populate_id").(string),
SelfServiceRegistrationValidationId: data.Get("lambda_configuration.0.self_service_registration_validation_id").(string),
UserinfoPopulateId: data.Get("lambda_configuration.0.userinfo_populate_id").(string),
},
LoginConfiguration: fusionauth.LoginConfiguration{
AllowTokenRefresh: data.Get("login_configuration.0.allow_token_refresh").(bool),
Expand All @@ -67,21 +68,25 @@ func buildApplication(data *schema.ResourceData) fusionauth.Application {
AuthorizedURLValidationPolicy: fusionauth.Oauth2AuthorizedURLValidationPolicy(data.Get("oauth_configuration.0.authorized_url_validation_policy").(string)),
ClientAuthenticationPolicy: fusionauth.ClientAuthenticationPolicy(data.Get("oauth_configuration.0.client_authentication_policy").(string)),
ClientSecret: data.Get("oauth_configuration.0.client_secret").(string),
ConsentMode: fusionauth.OAuthScopeConsentMode(data.Get("oauth_configuration.0.consent_mode").(string)),
Debug: data.Get("oauth_configuration.0.debug").(bool),
DeviceVerificationURL: data.Get("oauth_configuration.0.device_verification_url").(string),
EnabledGrants: buildGrants("oauth_configuration.0.enabled_grants", data),
GenerateRefreshTokens: data.Get("oauth_configuration.0.generate_refresh_tokens").(bool),
LogoutBehavior: fusionauth.LogoutBehavior(data.Get("oauth_configuration.0.logout_behavior").(string)),
LogoutURL: data.Get("oauth_configuration.0.logout_url").(string),
ProofKeyForCodeExchangePolicy: fusionauth.ProofKeyForCodeExchangePolicy(data.Get("oauth_configuration.0.proof_key_for_code_exchange_policy").(string)),
RequireClientAuthentication: data.Get("oauth_configuration.0.require_client_authentication").(bool),
LogoutBehavior: fusionauth.LogoutBehavior(data.Get("oauth_configuration.0.logout_behavior").(string)),
EnabledGrants: buildGrants("oauth_configuration.0.enabled_grants", data),
RequireRegistration: data.Get("oauth_configuration.0.require_registration").(bool),
ProvidedScopePolicy: fusionauth.ProvidedScopePolicy{
Address: buildRequireable("oauth_configuration.0.provided_scope_policy.0.address", data),
Email: buildRequireable("oauth_configuration.0.provided_scope_policy.0.email", data),
Phone: buildRequireable("oauth_configuration.0.provided_scope_policy.0.phone", data),
Profile: buildRequireable("oauth_configuration.0.provided_scope_policy.0.profile", data),
},
Relationship: fusionauth.OAuthApplicationRelationship(data.Get("oauth_configuration.0.relationship").(string)),
RequireClientAuthentication: data.Get("oauth_configuration.0.require_client_authentication").(bool),
RequireRegistration: data.Get("oauth_configuration.0.require_registration").(bool),
ScopeHandlingPolicy: fusionauth.OAuthScopeHandlingPolicy(data.Get("oauth_configuration.0.scope_handling_policy").(string)),
UnknownScopePolicy: fusionauth.UnknownScopePolicy(data.Get("oauth_configuration.0.unknown_scope_policy").(string)),
},
PasswordlessConfiguration: fusionauth.PasswordlessConfiguration{
Enableable: buildEnableable("passwordless_configuration_enabled", data),
Expand Down Expand Up @@ -256,6 +261,7 @@ func buildResourceDataFromApplication(a fusionauth.Application, data *schema.Res
"id_token_populate_id": a.LambdaConfiguration.IdTokenPopulateId,
"samlv2_populate_id": a.LambdaConfiguration.Samlv2PopulateId,
"self_service_registration_validation_id": a.LambdaConfiguration.SelfServiceRegistrationValidationId,
"userinfo_populate_id": a.LambdaConfiguration.UserinfoPopulateId,
},
})
if err != nil {
Expand Down Expand Up @@ -297,15 +303,47 @@ func buildResourceDataFromApplication(a fusionauth.Application, data *schema.Res
"client_authentication_policy": a.OauthConfiguration.ClientAuthenticationPolicy,
"client_secret": a.OauthConfiguration.ClientSecret,
"client_id": a.OauthConfiguration.ClientId,
"consent_mode": a.OauthConfiguration.ConsentMode,
"debug": a.OauthConfiguration.Debug,
"device_verification_url": a.OauthConfiguration.DeviceVerificationURL,
"enabled_grants": a.OauthConfiguration.EnabledGrants,
"generate_refresh_tokens": a.OauthConfiguration.GenerateRefreshTokens,
"logout_url": a.OauthConfiguration.LogoutURL,
"require_client_authentication": a.OauthConfiguration.RequireClientAuthentication,
"logout_behavior": a.OauthConfiguration.LogoutBehavior,
"enabled_grants": a.OauthConfiguration.EnabledGrants,
"require_registration": a.OauthConfiguration.RequireRegistration,
"logout_url": a.OauthConfiguration.LogoutURL,
"proof_key_for_code_exchange_policy": a.OauthConfiguration.ProofKeyForCodeExchangePolicy,
"provided_scope_policy": []map[string]interface{}{
{
"address": []map[string]interface{}{
{
"enabled": a.OauthConfiguration.ProvidedScopePolicy.Address.Enabled,
"required": a.OauthConfiguration.ProvidedScopePolicy.Address.Required,
},
},
"email": []map[string]interface{}{
{
"enabled": a.OauthConfiguration.ProvidedScopePolicy.Email.Enabled,
"required": a.OauthConfiguration.ProvidedScopePolicy.Email.Required,
},
},
"phone": []map[string]interface{}{
{
"enabled": a.OauthConfiguration.ProvidedScopePolicy.Phone.Enabled,
"required": a.OauthConfiguration.ProvidedScopePolicy.Phone.Required,
},
},
"profile": []map[string]interface{}{
{
"enabled": a.OauthConfiguration.ProvidedScopePolicy.Profile.Enabled,
"required": a.OauthConfiguration.ProvidedScopePolicy.Profile.Required,
},
},
},
},
"relationship": a.OauthConfiguration.Relationship,
"require_client_authentication": a.OauthConfiguration.RequireClientAuthentication,
"require_registration": a.OauthConfiguration.RequireRegistration,
"scope_handling_policy": a.OauthConfiguration.ScopeHandlingPolicy,
"unknown_scope_policy": a.OauthConfiguration.UnknownScopePolicy,
},
})
if err != nil {
Expand Down