diff --git a/.env.sample b/.env.sample index 9566389ec3..85576172f2 100644 --- a/.env.sample +++ b/.env.sample @@ -7,7 +7,7 @@ DJANGO_DB_DIR=. DJANGO_DB_FILE=django.db DJANGO_DB_FIXTURES="benefits/core/migrations/local_fixtures.json" -auth_provider_client_id=benefits-oauth-client-id +claims_provider_client_id=benefits-oauth-client-id agency_card_verifier_api_auth_key=server-auth-token client_private_key='-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1pt0ZoOuPEVPJJS+5r884zcjZLkZZ2GcPwr79XOLDbOi46on\nCa79kjRnhS0VUK96SwUPS0z9J5mDA5LSNL2RoxFb5QGaevnJY828NupzTNdUd0sY\nJK3kRjKUggHWuB55hwJcH/Dx7I3DNH4NL68UAlK+VjwJkfYPrhq/bl5z8ZiurvBa\n5C1mDxhFpcTZlCfxQoas7D1d+uPACF6mEMbQNd3RaIaSREO50NvNywXIIt/OmCiR\nqI7JtOcn4eyh1I4j9WtlbMhRJLfwPMAgY5epTsWcURmhVofF2wVoFbib3JGCfA7t\nz/gmP5YoEKnf/cumKmF3e9LrZb8zwm7bTHUViwIDAQABAoIBAQCIv0XMjNvZS9DC\nXoXGQtVpcxj6dXfaiDgnc7hZDubsNCr3JtT5NqgdIYdVNQUABNDIPNEiCkzFjuwM\nuuF2+dRzM/x6UCs/cSsCjXYBCCOwMwV/fjpEJQnwMQqwTLulVsXZYYeSUtXVBf/8\n0tVULRty34apLFhsyX30UtboXQdESfpmm5ZsqsZJlYljw+M7JxRMneQclI19y/ya\nhPWlfhLB9OffVEJXGaWx1NSYnKoCMKqE/+4krROr6V62xXaNyX6WtU6XiT7C6R5A\nPBxfhmoeFdVCF6a+Qq0v2fKThYoZnV4sn2q2An9YPfynFYnlgzdfnAFSejsqxQd0\nfxYLOtMBAoGBAP1jxjHDJngZ1N+ymw9MIpRgr3HeuMP5phiSTbY2tu9lPzQd+TMX\nfhr1bQh2Fd/vU0u7X0yPnTWtUrLlCdGnWPpXivx95GNGgUUIk2HStFdrRx+f2Qvk\nG8vtLgmSbjQ26UiHzxi9Wa0a41PWIA3TixkcFrS2X29Qc4yd6pVHmicfAoGBANjR\nZ8aaDkSKLkq5Nk1T7I0E1+mtPoH1tPV/FJClXjJrvfDuYHBeOyUpipZddnZuPGWA\nIW2tFIsMgJQtgpvgs52NFI7pQGJRUPK/fTG+Ycocxo78TkLr/RIj8Kj5brXsbZ9P\n3/WBX5GAISTSp1ab8xVgK/Tm07hGupKVqnY2lCAVAoGAIql0YjhE2ecGtLcU+Qm8\nLTnwpg4GjmBnNTNGSCfB7IuYEsQK489R49Qw3xhwM5rkdRajmbCHm+Eiz+/+4NwY\nkt5I1/NMu7vYUR40MwyEuPSm3Q+bvEGu/71pL8wFIUVlshNJ5CN60fA8qqo+5kVK\n4Ntzy7Kq6WpC9Dhh75vE3ZcCgYEAty99uXtxsJD6+aEwcvcENkUwUztPQ6ggAwci\nje9Z/cmwCj6s9mN3HzfQ4qgGrZsHpk4ycCK655xhilBFOIQJ3YRUKUaDYk4H0YDe\nOsf6gTP8wtQDH2GZSNlavLk5w7UFDYQD2b47y4fw+NaOEYvjPl0p5lmb6ebAPZb8\nFbKZRd0CgYBC1HTbA+zMEqDdY4MWJJLC6jZsjdxOGhzjrCtWcIWEGMDF7oDDEoix\nW3j2hwm4C6vaNkH9XX1dr5+q6gq8vJQdbYoExl22BGMiNbfI3+sLRk0zBYL//W6c\ntSREgR4EjosqQfbkceLJ2JT1wuNjInI0eR9H3cRugvlDTeWtbdJ5qA==\n-----END RSA PRIVATE KEY-----' client_public_key='-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1pt0ZoOuPEVPJJS+5r88\n4zcjZLkZZ2GcPwr79XOLDbOi46onCa79kjRnhS0VUK96SwUPS0z9J5mDA5LSNL2R\noxFb5QGaevnJY828NupzTNdUd0sYJK3kRjKUggHWuB55hwJcH/Dx7I3DNH4NL68U\nAlK+VjwJkfYPrhq/bl5z8ZiurvBa5C1mDxhFpcTZlCfxQoas7D1d+uPACF6mEMbQ\nNd3RaIaSREO50NvNywXIIt/OmCiRqI7JtOcn4eyh1I4j9WtlbMhRJLfwPMAgY5ep\nTsWcURmhVofF2wVoFbib3JGCfA7tz/gmP5YoEKnf/cumKmF3e9LrZb8zwm7bTHUV\niwIDAQAB\n-----END PUBLIC KEY-----' diff --git a/benefits/core/admin.py b/benefits/core/admin.py index bd30b00ebd..6ad65f9193 100644 --- a/benefits/core/admin.py +++ b/benefits/core/admin.py @@ -21,8 +21,8 @@ admin.site.register(models.PemData) -@admin.register(models.AuthProvider) -class AuthProviderAdmin(admin.ModelAdmin): # pragma: no cover +@admin.register(models.ClaimsProvider) +class ClaimsProviderAdmin(admin.ModelAdmin): # pragma: no cover def get_exclude(self, request, obj=None): if not request.user.is_superuser: return ["client_id_secret_name"] @@ -80,7 +80,7 @@ def get_readonly_fields(self, request, obj=None): if not request.user.is_superuser: return [ "api_url", - "auth_provider", + "claims_provider", "selection_label_template", "start_template", "unverified_template", diff --git a/benefits/core/context_processors.py b/benefits/core/context_processors.py index 0d54ae5f14..79fdffe014 100644 --- a/benefits/core/context_processors.py +++ b/benefits/core/context_processors.py @@ -55,9 +55,9 @@ def authentication(request): "logged_in": session.logged_in(request), } - if verifier.is_auth_required: - data["sign_out_button_template"] = verifier.auth_provider.sign_out_button_template - data["sign_out_link_template"] = verifier.auth_provider.sign_out_link_template + if verifier.uses_claims_verification: + data["sign_out_button_template"] = verifier.claims_provider.sign_out_button_template + data["sign_out_link_template"] = verifier.claims_provider.sign_out_link_template return {"authentication": data} else: diff --git a/benefits/core/middleware.py b/benefits/core/middleware.py index 144545870e..4d2e18de3b 100644 --- a/benefits/core/middleware.py +++ b/benefits/core/middleware.py @@ -129,9 +129,9 @@ class LoginRequired(MiddlewareMixin): """Middleware that checks whether a user is logged in.""" def process_view(self, request, view_func, view_args, view_kwargs): - # only require login if verifier requires it + # only require login if verifier uses claims verification verifier = session.verifier(request) - if not verifier or not verifier.is_auth_required or session.logged_in(request): + if not verifier or not verifier.uses_claims_verification or session.logged_in(request): # pass through return None diff --git a/benefits/core/migrations/0017_refactor_authprovider_claimsprovider.py b/benefits/core/migrations/0017_refactor_authprovider_claimsprovider.py new file mode 100644 index 0000000000..7e8da3543e --- /dev/null +++ b/benefits/core/migrations/0017_refactor_authprovider_claimsprovider.py @@ -0,0 +1,111 @@ +# Generated by Django 5.0.7 on 2024-08-02 22:52 + +from django.contrib.auth.management import create_permissions +from django.db import migrations, models + +import benefits.core.models +import benefits.secrets + + +def create_all_permissions(apps, schema_editor): + for app_config in apps.get_app_configs(): + app_config.models_module = True + create_permissions(app_config, apps=apps, verbosity=0) + app_config.models_module = None + + +def update_permissions(apps, schema_editor): + # delete old permissions + Permission = apps.get_model("auth", "Permission") + old_permission_names = [ + "Can view auth provider", + "Can change auth provider", + "Can add auth provider", + "Can delete auth provider", + ] + + for name in old_permission_names: + old_permission = Permission.objects.get(name=name) + old_permission.delete() + + # add new permissions to staff group + Group = apps.get_model("auth", "Group") + staff_group = Group.objects.get(name="Cal-ITP") + + Permission = apps.get_model("auth", "Permission") + new_permission_names = ["Can view claims provider", "Can change claims provider"] + + for name in new_permission_names: + new_permission = Permission.objects.get(name=name) + staff_group.permissions.add(new_permission) + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0016_refactor_paymentprocessor_transitprocessor"), + ] + + operations = [ + migrations.RenameModel( + old_name="AuthProvider", + new_name="ClaimsProvider", + ), + migrations.RunPython(create_all_permissions), # this is needed to create the new permissions for the renamed model + migrations.RunPython(update_permissions), + migrations.AlterField( + model_name="claimsprovider", + name="authority", + field=models.TextField(help_text="The fully qualified HTTPS domain name for an OAuth authority server"), + ), + migrations.AlterField( + model_name="claimsprovider", + name="claim", + field=models.TextField( + blank=True, help_text="The name of the claim (name/value pair) that is used to verify eligibility", null=True + ), + ), + migrations.AlterField( + model_name="claimsprovider", + name="client_id_secret_name", + field=benefits.core.models.SecretNameField( + help_text="The name of the secret containing the client ID for this claims provider", + max_length=127, + validators=[benefits.secrets.SecretNameValidator()], + ), + ), + migrations.AlterField( + model_name="claimsprovider", + name="client_name", + field=models.TextField(help_text="Unique identifier used to register this claims provider with Authlib registry"), + ), + migrations.AlterField( + model_name="claimsprovider", + name="scheme", + field=models.TextField(help_text="The authentication scheme to use"), + ), + migrations.AlterField( + model_name="claimsprovider", + name="scope", + field=models.TextField( + blank=True, + help_text="A space-separated list of identifiers used to specify what access privileges are being requested", + null=True, + ), + ), + migrations.AlterField( + model_name="claimsprovider", + name="sign_out_button_template", + field=models.TextField(blank=True, help_text="Template that renders sign-out button", null=True), + ), + migrations.AlterField( + model_name="claimsprovider", + name="sign_out_link_template", + field=models.TextField(blank=True, help_text="Template that renders sign-out link", null=True), + ), + migrations.RenameField( + model_name="eligibilityverifier", + old_name="auth_provider", + new_name="claims_provider", + ), + ] diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 9dcf369bb0..526a19f228 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -27,13 +27,13 @@ } }, { - "model": "core.authprovider", + "model": "core.claimsprovider", "pk": 1, "fields": { "sign_out_button_template": "core/includes/button--sign-out--login-gov.html", "sign_out_link_template": "core/includes/link--sign-out--login-gov.html", "client_name": "senior-benefits-oauth-client-name", - "client_id_secret_name": "auth-provider-client-id", + "client_id_secret_name": "claims-provider-client-id", "authority": "https://example.com", "scope": "verify:senior", "claim": "senior", @@ -41,13 +41,13 @@ } }, { - "model": "core.authprovider", + "model": "core.claimsprovider", "pk": 2, "fields": { "sign_out_button_template": "core/includes/button--sign-out--login-gov.html", "sign_out_link_template": "core/includes/link--sign-out--login-gov.html", "client_name": "veteran-benefits-oauth-client-name", - "client_id_secret_name": "auth-provider-client-id", + "client_id_secret_name": "claims-provider-client-id", "authority": "https://example.com", "scope": "verify:veteran", "claim": "veteran", @@ -55,13 +55,13 @@ } }, { - "model": "core.authprovider", + "model": "core.claimsprovider", "pk": 3, "fields": { "sign_out_button_template": "core/includes/button--sign-out--login-gov.html", "sign_out_link_template": "core/includes/link--sign-out--login-gov.html", "client_name": "calfresh-benefits-oauth-client-name", - "client_id_secret_name": "auth-provider-client-id", + "client_id_secret_name": "claims-provider-client-id", "authority": "https://example.com", "scope": "verify:calfresh", "claim": "calfresh", @@ -128,7 +128,7 @@ "jwe_cek_enc": null, "jwe_encryption_alg": null, "jws_signing_alg": null, - "auth_provider": 1, + "claims_provider": 1, "selection_label_template": "eligibility/includes/selection-label--senior.html", "start_template": "eligibility/start--senior.html", "form_class": null @@ -149,7 +149,7 @@ "jwe_cek_enc": null, "jwe_encryption_alg": null, "jws_signing_alg": null, - "auth_provider": 2, + "claims_provider": 2, "selection_label_template": "eligibility/includes/selection-label--veteran.html", "start_template": "eligibility/start--veteran.html", "form_class": null @@ -170,7 +170,7 @@ "jwe_cek_enc": "A256CBC-HS512", "jwe_encryption_alg": "RSA-OAEP", "jws_signing_alg": "RS256", - "auth_provider": null, + "claims_provider": null, "selection_label_template": "eligibility/includes/selection-label--cst-agency-card.html", "start_template": "eligibility/start--cst-agency-card.html", "form_class": "benefits.eligibility.forms.CSTAgencyCard", @@ -193,7 +193,7 @@ "jwe_cek_enc": null, "jwe_encryption_alg": null, "jws_signing_alg": null, - "auth_provider": 3, + "claims_provider": 3, "selection_label_template": "eligibility/includes/selection-label--calfresh.html", "start_template": "eligibility/start--calfresh.html", "form_class": null, diff --git a/benefits/core/models.py b/benefits/core/models.py index bfdfda0c3b..397a265b16 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -75,18 +75,26 @@ def data(self): return secret_data if secret_data is not None else remote_data -class AuthProvider(models.Model): - """An entity that provides authentication for eligibility verifiers.""" +class ClaimsProvider(models.Model): + """An entity that provides claims for eligibility verification.""" id = models.AutoField(primary_key=True) - sign_out_button_template = models.TextField(null=True, blank=True) - sign_out_link_template = models.TextField(null=True, blank=True) - client_name = models.TextField() - client_id_secret_name = SecretNameField() - authority = models.TextField() - scope = models.TextField(null=True, blank=True) - claim = models.TextField(null=True, blank=True) - scheme = models.TextField() + sign_out_button_template = models.TextField(null=True, blank=True, help_text="Template that renders sign-out button") + sign_out_link_template = models.TextField(null=True, blank=True, help_text="Template that renders sign-out link") + client_name = models.TextField(help_text="Unique identifier used to register this claims provider with Authlib registry") + client_id_secret_name = SecretNameField( + help_text="The name of the secret containing the client ID for this claims provider" + ) + authority = models.TextField(help_text="The fully qualified HTTPS domain name for an OAuth authority server") + scope = models.TextField( + null=True, + blank=True, + help_text="A space-separated list of identifiers used to specify what access privileges are being requested", + ) + claim = models.TextField( + null=True, blank=True, help_text="The name of the claim (name/value pair) that is used to verify eligibility" + ) + scheme = models.TextField(help_text="The authentication scheme to use") @property def supports_claims_verification(self): @@ -179,7 +187,7 @@ class EligibilityVerifier(models.Model): jwe_encryption_alg = models.TextField(null=True, blank=True) # The JWS-compatible signing algorithm jws_signing_alg = models.TextField(null=True, blank=True) - auth_provider = models.ForeignKey(AuthProvider, on_delete=models.PROTECT, null=True, blank=True) + claims_provider = models.ForeignKey(ClaimsProvider, on_delete=models.PROTECT, null=True, blank=True) selection_label_template = models.TextField() start_template = models.TextField(null=True, blank=True) # reference to a form class used by this Verifier, e.g. benefits.app.forms.FormClass @@ -206,14 +214,9 @@ def public_key_data(self): return self.public_key.data @property - def is_auth_required(self): - """True if this Verifier requires authentication. False otherwise.""" - return self.auth_provider is not None - - @property - def uses_auth_verification(self): - """True if this Verifier verifies via the auth provider. False otherwise.""" - return self.is_auth_required and self.auth_provider.supports_claims_verification + def uses_claims_verification(self): + """True if this Verifier verifies via the claims provider. False otherwise.""" + return self.claims_provider is not None and self.claims_provider.supports_claims_verification def form_instance(self, *args, **kwargs): """Return an instance of this verifier's form, or None.""" diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py index 06b0a83978..d70674bdd9 100644 --- a/benefits/eligibility/verify.py +++ b/benefits/eligibility/verify.py @@ -30,7 +30,7 @@ def eligibility_from_api(verifier, form, agency): def eligibility_from_oauth(verifier, oauth_claim, agency): - if verifier.uses_auth_verification and verifier.auth_provider.claim == oauth_claim: + if verifier.uses_claims_verification and verifier.claims_provider.claim == oauth_claim: return agency.type_names_to_verify(verifier) else: return [] diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py index a5b4767529..2213121434 100644 --- a/benefits/eligibility/views.py +++ b/benefits/eligibility/views.py @@ -102,7 +102,7 @@ def confirm(request): types_to_verify = agency.type_names_to_verify(verifier) # GET for OAuth verification - if request.method == "GET" and verifier.uses_auth_verification: + if request.method == "GET" and verifier.uses_claims_verification: analytics.started_eligibility(request, types_to_verify) verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request), agency) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index e4df4d0137..40a2b82627 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -246,7 +246,7 @@ def reenrollment_error(request): if eligibility.reenrollment_error_template is None: raise Exception(f"Re-enrollment error with null template on: {eligibility.label}") - if session.logged_in(request) and verifier.auth_provider.supports_sign_out: + if session.logged_in(request) and verifier.claims_provider.supports_sign_out: # overwrite origin for a logged in user # if they click the logout button, they are taken to the new route session.update(request, origin=reverse(ROUTE_LOGGED_OUT)) @@ -285,7 +285,7 @@ def success(request): eligibility = session.eligibility(request) verifier = session.verifier(request) - if session.logged_in(request) and verifier.auth_provider.supports_sign_out: + if session.logged_in(request) and verifier.claims_provider.supports_sign_out: # overwrite origin for a logged in user # if they click the logout button, they are taken to the new route session.update(request, origin=reverse(ROUTE_LOGGED_OUT)) diff --git a/benefits/oauth/analytics.py b/benefits/oauth/analytics.py index 22bfbeeb95..c687894831 100644 --- a/benefits/oauth/analytics.py +++ b/benefits/oauth/analytics.py @@ -11,8 +11,8 @@ class OAuthEvent(core.Event): def __init__(self, request, event_type): super().__init__(request, event_type) verifier = session.verifier(request) - if verifier and verifier.uses_auth_verification: - self.update_event_properties(auth_provider=verifier.auth_provider.client_name) + if verifier and verifier.uses_claims_verification: + self.update_event_properties(auth_provider=verifier.claims_provider.client_name) class OAuthErrorEvent(OAuthEvent): diff --git a/benefits/oauth/client.py b/benefits/oauth/client.py index 923b29ab63..596be9ce0d 100644 --- a/benefits/oauth/client.py +++ b/benefits/oauth/client.py @@ -41,7 +41,7 @@ def _authorize_params(scheme): def _register_provider(oauth_registry, provider): """ - Register OAuth clients into the given registry, using configuration from AuthProvider model. + Register OAuth clients into the given registry, using configuration from ClaimsProvider model. Adapted from https://stackoverflow.com/a/64174413. """ diff --git a/benefits/oauth/middleware.py b/benefits/oauth/middleware.py index 81b972bc89..25478ce3db 100644 --- a/benefits/oauth/middleware.py +++ b/benefits/oauth/middleware.py @@ -24,11 +24,11 @@ def process_request(self, request): verifier = session.verifier(request) - if verifier.uses_auth_verification: + if verifier.uses_claims_verification: # all good, the chosen verifier is configured correctly return None elif not (verifier.api_url or verifier.form_class): - # the chosen verifier doesn't have Eligibility API config OR auth provider config + # the chosen verifier doesn't have Eligibility API config OR claims provider config # this is likely a misconfiguration on the backend, not a user error message = f"Verifier with no API or IDP config: {verifier.name} (id={verifier.id})" analytics.error(request, message=message, operation=request.path) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index e27cebf934..aa0e32ef6f 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -24,7 +24,7 @@ TEMPLATE_SYSTEM_ERROR = "oauth/system_error.html" -def _oauth_client_or_error_redirect(request, auth_provider): +def _oauth_client_or_error_redirect(request, claims_provider): """Calls `benefits.oauth.client.create_client()`. If a client is created successfully, return it; Otherwise, return a redirect response to the `oauth:system-error` route. @@ -34,12 +34,12 @@ def _oauth_client_or_error_redirect(request, auth_provider): exception = None try: - oauth_client = create_client(oauth, auth_provider) + oauth_client = create_client(oauth, claims_provider) except Exception as ex: exception = ex if not oauth_client and not exception: - exception = Exception(f"oauth_client not registered: {auth_provider.client_name}") + exception = Exception(f"oauth_client not registered: {claims_provider.client_name}") if exception: analytics.error(request, message=str(exception), operation="init") @@ -54,7 +54,7 @@ def login(request): """View implementing OIDC authorize_redirect.""" verifier = session.verifier(request) - oauth_client_result = _oauth_client_or_error_redirect(request, verifier.auth_provider) + oauth_client_result = _oauth_client_or_error_redirect(request, verifier.claims_provider) if hasattr(oauth_client_result, "authorize_redirect"): # this looks like an oauth_client since it has the method we need @@ -95,7 +95,7 @@ def authorize(request): """View implementing OIDC token authorization.""" verifier = session.verifier(request) - oauth_client_result = _oauth_client_or_error_redirect(request, verifier.auth_provider) + oauth_client_result = _oauth_client_or_error_redirect(request, verifier.claims_provider) if hasattr(oauth_client_result, "authorize_access_token"): # this looks like an oauth_client since it has the method we need @@ -128,7 +128,7 @@ def authorize(request): id_token = token["id_token"] # We store the returned claim in case it can be used later in eligibility verification. - verifier_claim = verifier.auth_provider.claim + verifier_claim = verifier.claims_provider.claim stored_claim = None error_claim = None @@ -168,7 +168,7 @@ def logout(request): """View implementing OIDC and application sign out.""" verifier = session.verifier(request) - oauth_client_result = _oauth_client_or_error_redirect(request, verifier.auth_provider) + oauth_client_result = _oauth_client_or_error_redirect(request, verifier.claims_provider) if hasattr(oauth_client_result, "load_server_metadata"): # this looks like an oauth_client since it has the method we need diff --git a/docs/README.md b/docs/README.md index 24861359b7..2475451283 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,7 +38,7 @@ Read more about each [enrollment pathway](/benefits/enrollment-pathways/). ## Technical and security details -`benefits` is a [Django 5][django] web application. The application talks to one or more [Eligibility Verification APIs](https://docs.calitp.org/eligibility-api/specification) or authentication providers. These APIs and the application itself are +`benefits` is a [Django 5][django] web application. The application talks to one or more [Eligibility Verification APIs](https://docs.calitp.org/eligibility-api/specification) or claims providers. These APIs and the application itself are designed for privacy and security of user information: - The API communicates with signed and encrypted JSON Web Tokens containing only the most necessary of user data for the purpose of eligibility verification diff --git a/docs/configuration/oauth.md b/docs/configuration/oauth.md index e9345d16cb..6fc621c37d 100644 --- a/docs/configuration/oauth.md +++ b/docs/configuration/oauth.md @@ -22,9 +22,9 @@ for more details about what features are available. Specifically, from Authlib w ## Django configuration -OAuth settings are configured as instances of the [`AuthProvider` model](../development/models-migrations.md). +OAuth settings are configured as instances of the [`ClaimsProvider` model](../development/models-migrations.md). -The [data migration file](./data.md) contains sample values for an `AuthProvider` configuration. You can set values for a real Open ID Connect provider in environment variables so that they are used instead of the sample values. +The [data migration file](./data.md) contains sample values for a `ClaimsProvider` configuration. You can set values for a real Open ID Connect provider in environment variables so that they are used instead of the sample values. ## Django usage @@ -34,6 +34,6 @@ use in e.g. views. - `oauth` is an `authlib.integrations.django_client.OAuth` instance Consumers call `benefits.oauth.client.create_client(oauth, provider)` with the name of a client to obtain an Authlib client -instance. If that client name has not been registered yet, `_register_provider(oauth_registry, provider)` uses data from the given `AuthProvider` instance to register the client into this instance and returns the client object. +instance. If that client name has not been registered yet, `_register_provider(oauth_registry, provider)` uses data from the given `ClaimsProvider` instance to register the client into this instance and returns the client object. [oauth-client]: https://github.com/cal-itp/benefits/blob/main/benefits/oauth/client.py diff --git a/docs/configuration/transit-agency.md b/docs/configuration/transit-agency.md index a725545326..1a5f9471b1 100644 --- a/docs/configuration/transit-agency.md +++ b/docs/configuration/transit-agency.md @@ -18,8 +18,8 @@ Note that a `TransitAgency` model requires: - `help_template`: _Required for agencies_ - Agency-specific help questions and answers - `selection_label_template`: _Required for verifiers_ - Text and optional modals for the radio button form on the Eligibility Index page - `start_template`: _Required for verifiers_ - Text and optional custom styles for call to action button on the Eligibility Start page - - `sign_out_button_template`: _Required for auth providers_ - Sign out link button, used on any page after sign in - - `sign_out_link_template`: _Required for auth providers_ - Sign out link text, used on any page after sign in + - `sign_out_button_template`: _Required for claims providers_ - Sign out link button, used on any page after sign in + - `sign_out_link_template`: _Required for claims providers_ - Sign out link text, used on any page after sign in Also note that these steps assume the transit agency is using Littlepay as their transit processor. Support for integration with [other transit processors](https://www.camobilitymarketplace.org/contracts/) may be added in the future. diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 35463edb1e..4e05c48c44 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -7,7 +7,7 @@ from pytest_socket import disable_socket from benefits.core import session -from benefits.core.models import AuthProvider, EligibilityType, EligibilityVerifier, TransitProcessor, PemData, TransitAgency +from benefits.core.models import ClaimsProvider, EligibilityType, EligibilityVerifier, TransitProcessor, PemData, TransitAgency def pytest_runtest_setup(): @@ -47,8 +47,8 @@ def model_PemData(): @pytest.fixture -def model_AuthProvider(): - auth_provider = AuthProvider.objects.create( +def model_ClaimsProvider(): + claims_provider = ClaimsProvider.objects.create( sign_out_button_template="core/includes/button--sign-out--senior.html", sign_out_link_template="core/includes/link--sign-out--senior.html", client_name="Client", @@ -56,47 +56,47 @@ def model_AuthProvider(): authority="https://example.com", ) - return auth_provider + return claims_provider @pytest.fixture -def model_AuthProvider_with_verification(model_AuthProvider): - model_AuthProvider.scope = "scope" - model_AuthProvider.claim = "claim" - model_AuthProvider.save() +def model_ClaimsProvider_with_scope_and_claim(model_ClaimsProvider): + model_ClaimsProvider.scope = "scope" + model_ClaimsProvider.claim = "claim" + model_ClaimsProvider.save() - return model_AuthProvider + return model_ClaimsProvider @pytest.fixture -def model_AuthProvider_with_verification_no_sign_out(model_AuthProvider): - model_AuthProvider.scope = "scope" - model_AuthProvider.claim = "claim" - model_AuthProvider.sign_out_button_template = None - model_AuthProvider.sign_out_link_template = None - model_AuthProvider.save() +def model_ClaimsProvider_with_scope_and_claim_no_sign_out(model_ClaimsProvider): + model_ClaimsProvider.scope = "scope" + model_ClaimsProvider.claim = "claim" + model_ClaimsProvider.sign_out_button_template = None + model_ClaimsProvider.sign_out_link_template = None + model_ClaimsProvider.save() - return model_AuthProvider + return model_ClaimsProvider @pytest.fixture -def model_AuthProvider_without_verification(model_AuthProvider): - model_AuthProvider.scope = None - model_AuthProvider.claim = None - model_AuthProvider.save() +def model_ClaimsProvider_no_scope_and_claim(model_ClaimsProvider): + model_ClaimsProvider.scope = None + model_ClaimsProvider.claim = None + model_ClaimsProvider.save() - return model_AuthProvider + return model_ClaimsProvider @pytest.fixture -def model_AuthProvider_without_verification_no_sign_out(model_AuthProvider): - model_AuthProvider.scope = None - model_AuthProvider.claim = None - model_AuthProvider.sign_out_button_template = None - model_AuthProvider.sign_out_link_template = None - model_AuthProvider.save() +def model_ClaimsProvider_no_scope_and_claim_no_sign_out(model_ClaimsProvider): + model_ClaimsProvider.scope = None + model_ClaimsProvider.claim = None + model_ClaimsProvider.sign_out_button_template = None + model_ClaimsProvider.sign_out_link_template = None + model_ClaimsProvider.save() - return model_AuthProvider + return model_ClaimsProvider @pytest.fixture @@ -168,8 +168,10 @@ def model_EligibilityVerifier(model_PemData, model_EligibilityType): @pytest.fixture -def model_EligibilityVerifier_AuthProvider_with_verification(model_AuthProvider_with_verification, model_EligibilityVerifier): - model_EligibilityVerifier.auth_provider = model_AuthProvider_with_verification +def model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim( + model_ClaimsProvider_with_scope_and_claim, model_EligibilityVerifier +): + model_EligibilityVerifier.claims_provider = model_ClaimsProvider_with_scope_and_claim model_EligibilityVerifier.save() return model_EligibilityVerifier @@ -276,34 +278,36 @@ def mocked_session_verifier(mocker, model_EligibilityVerifier): @pytest.fixture -def mocked_session_verifier_oauth(mocker, model_EligibilityVerifier_AuthProvider_with_verification): +def mocked_session_verifier_oauth(mocker, model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim): return mocker.patch( - "benefits.core.session.verifier", autospec=True, return_value=model_EligibilityVerifier_AuthProvider_with_verification + "benefits.core.session.verifier", + autospec=True, + return_value=model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim, ) @pytest.fixture -def mocked_session_verifier_uses_auth_verification( - model_EligibilityVerifier_AuthProvider_with_verification, mocked_session_verifier_oauth +def mocked_session_verifier_uses_claims_verification( + model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim, mocked_session_verifier_oauth ): - mock_verifier = model_EligibilityVerifier_AuthProvider_with_verification - mock_verifier.name = model_EligibilityVerifier_AuthProvider_with_verification.name - mock_verifier.auth_provider.sign_out_button_template = ( - model_EligibilityVerifier_AuthProvider_with_verification.auth_provider.sign_out_button_template + mock_verifier = model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim + mock_verifier.name = model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim.name + mock_verifier.claims_provider.sign_out_button_template = ( + model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim.claims_provider.sign_out_button_template ) - mock_verifier.auth_provider.sign_out_link_template = ( - model_EligibilityVerifier_AuthProvider_with_verification.auth_provider.sign_out_link_template + mock_verifier.claims_provider.sign_out_link_template = ( + model_EligibilityVerifier_ClaimsProvider_with_scope_and_claim.claims_provider.sign_out_link_template ) mocked_session_verifier_oauth.return_value = mock_verifier return mocked_session_verifier_oauth @pytest.fixture -def mocked_session_verifier_does_not_use_auth_verification( - mocked_session_verifier_uses_auth_verification, model_AuthProvider_without_verification +def mocked_session_verifier_does_not_use_claims_verification( + mocked_session_verifier_uses_claims_verification, model_ClaimsProvider_no_scope_and_claim ): - mocked_verifier = mocked_session_verifier_uses_auth_verification - mocked_verifier.auth_provider = model_AuthProvider_without_verification + mocked_verifier = mocked_session_verifier_uses_claims_verification + mocked_verifier.claims_provider = model_ClaimsProvider_no_scope_and_claim return mocked_verifier diff --git a/tests/pytest/core/test_middleware_login_required.py b/tests/pytest/core/test_middleware_login_required.py index d10cf99a0e..c8ef7e456f 100644 --- a/tests/pytest/core/test_middleware_login_required.py +++ b/tests/pytest/core/test_middleware_login_required.py @@ -16,7 +16,7 @@ def decorated_view(mocked_view): @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_oauth") -def test_login_auth_required(app_request, mocked_view, decorated_view): +def test_login_verifier_uses_claims_verification(app_request, mocked_view, decorated_view): response = decorated_view(app_request) mocked_view.assert_not_called() @@ -26,9 +26,9 @@ def test_login_auth_required(app_request, mocked_view, decorated_view): @pytest.mark.django_db -def test_login_auth_not_required(app_request, model_EligibilityVerifier, mocked_view, decorated_view): - model_EligibilityVerifier.auth_provider = None - assert not model_EligibilityVerifier.is_auth_required +def test_login_verifier_does_not_use_claims_verification(app_request, model_EligibilityVerifier, mocked_view, decorated_view): + model_EligibilityVerifier.claims_provider = None + assert not model_EligibilityVerifier.uses_claims_verification session.update(app_request, verifier=model_EligibilityVerifier) decorated_view(app_request) diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py index c81b3f4f7c..3d61043385 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/core/test_models.py @@ -93,40 +93,40 @@ def test_PemData_data_text_secret_name_and_remote__uses_remote( @pytest.mark.django_db -def test_model_AuthProvider(model_AuthProvider): - assert not model_AuthProvider.supports_claims_verification - assert model_AuthProvider.supports_sign_out - assert str(model_AuthProvider) == model_AuthProvider.client_name +def test_model_ClaimsProvider(model_ClaimsProvider): + assert not model_ClaimsProvider.supports_claims_verification + assert model_ClaimsProvider.supports_sign_out + assert str(model_ClaimsProvider) == model_ClaimsProvider.client_name @pytest.mark.django_db -def test_model_AuthProvider_client_id(model_AuthProvider, mock_models_get_secret_by_name): - secret_value = model_AuthProvider.client_id +def test_model_ClaimsProvider_client_id(model_ClaimsProvider, mock_models_get_secret_by_name): + secret_value = model_ClaimsProvider.client_id - mock_models_get_secret_by_name.assert_called_once_with(model_AuthProvider.client_id_secret_name) + mock_models_get_secret_by_name.assert_called_once_with(model_ClaimsProvider.client_id_secret_name) assert secret_value == mock_models_get_secret_by_name.return_value @pytest.mark.django_db -def test_model_AuthProvider_with_verification(model_AuthProvider_with_verification): - assert model_AuthProvider_with_verification.supports_claims_verification +def test_model_ClaimsProvider_with_scope_and_claim(model_ClaimsProvider_with_scope_and_claim): + assert model_ClaimsProvider_with_scope_and_claim.supports_claims_verification @pytest.mark.django_db -def test_model_AuthProvider_with_verification_no_sign_out(model_AuthProvider_with_verification_no_sign_out): - assert model_AuthProvider_with_verification_no_sign_out.supports_claims_verification - assert not model_AuthProvider_with_verification_no_sign_out.supports_sign_out +def test_model_ClaimsProvider_with_scope_and_claim_no_sign_out(model_ClaimsProvider_with_scope_and_claim_no_sign_out): + assert model_ClaimsProvider_with_scope_and_claim_no_sign_out.supports_claims_verification + assert not model_ClaimsProvider_with_scope_and_claim_no_sign_out.supports_sign_out @pytest.mark.django_db -def test_model_AuthProvider_without_verification(model_AuthProvider_without_verification): - assert not model_AuthProvider_without_verification.supports_claims_verification +def test_model_ClaimsProvider_no_scope_and_claim(model_ClaimsProvider_no_scope_and_claim): + assert not model_ClaimsProvider_no_scope_and_claim.supports_claims_verification @pytest.mark.django_db -def test_model_AuthProvider_without_verification_no_sign_out(model_AuthProvider_without_verification_no_sign_out): - assert not model_AuthProvider_without_verification_no_sign_out.supports_claims_verification - assert not model_AuthProvider_without_verification_no_sign_out.supports_sign_out +def test_model_ClaimsProvider_no_scope_and_claim_no_sign_out(model_ClaimsProvider_no_scope_and_claim_no_sign_out): + assert not model_ClaimsProvider_no_scope_and_claim_no_sign_out.supports_claims_verification + assert not model_ClaimsProvider_no_scope_and_claim_no_sign_out.supports_sign_out @pytest.mark.django_db @@ -292,51 +292,46 @@ def test_EligibilityVerifier_by_id_nonmatching(): @pytest.mark.django_db -def test_EligibilityVerifier_with_AuthProvider_with_verification( - model_EligibilityVerifier, model_AuthProvider_with_verification +def test_EligibilityVerifier_with_ClaimsProvider_with_scope_and_claim( + model_EligibilityVerifier, model_ClaimsProvider_with_scope_and_claim ): - model_EligibilityVerifier.auth_provider = model_AuthProvider_with_verification + model_EligibilityVerifier.claims_provider = model_ClaimsProvider_with_scope_and_claim - assert model_EligibilityVerifier.is_auth_required - assert model_EligibilityVerifier.uses_auth_verification + assert model_EligibilityVerifier.uses_claims_verification @pytest.mark.django_db -def test_EligibilityVerifier_with_AuthProvider_with_verification_no_sign_out( - model_EligibilityVerifier, model_AuthProvider_with_verification_no_sign_out +def test_EligibilityVerifier_with_ClaimsProvider_with_scope_and_claim_no_sign_out( + model_EligibilityVerifier, model_ClaimsProvider_with_scope_and_claim_no_sign_out ): - model_EligibilityVerifier.auth_provider = model_AuthProvider_with_verification_no_sign_out + model_EligibilityVerifier.claims_provider = model_ClaimsProvider_with_scope_and_claim_no_sign_out - assert model_EligibilityVerifier.is_auth_required - assert model_EligibilityVerifier.uses_auth_verification + assert model_EligibilityVerifier.uses_claims_verification @pytest.mark.django_db -def test_EligibilityVerifier_with_AuthProvider_without_verification( - model_EligibilityVerifier, model_AuthProvider_without_verification +def test_EligibilityVerifier_with_ClaimsProvider_no_scope_and_claim( + model_EligibilityVerifier, model_ClaimsProvider_no_scope_and_claim ): - model_EligibilityVerifier.auth_provider = model_AuthProvider_without_verification + model_EligibilityVerifier.claims_provider = model_ClaimsProvider_no_scope_and_claim - assert model_EligibilityVerifier.is_auth_required - assert not model_EligibilityVerifier.uses_auth_verification + assert not model_EligibilityVerifier.uses_claims_verification @pytest.mark.django_db -def test_EligibilityVerifier_with_AuthProvider_without_verification_no_sign_out( - model_EligibilityVerifier, model_AuthProvider_without_verification_no_sign_out +def test_EligibilityVerifier_with_ClaimsProvider_no_scope_and_claim_no_sign_out( + model_EligibilityVerifier, model_ClaimsProvider_no_scope_and_claim_no_sign_out ): - model_EligibilityVerifier.auth_provider = model_AuthProvider_without_verification_no_sign_out + model_EligibilityVerifier.claims_provider = model_ClaimsProvider_no_scope_and_claim_no_sign_out - assert model_EligibilityVerifier.is_auth_required - assert not model_EligibilityVerifier.uses_auth_verification + assert not model_EligibilityVerifier.uses_claims_verification @pytest.mark.django_db -def test_EligibilityVerifier_without_AuthProvider(model_EligibilityVerifier): - model_EligibilityVerifier.auth_provider = None +def test_EligibilityVerifier_no_ClaimsProvider(model_EligibilityVerifier): + model_EligibilityVerifier.claims_provider = None - assert not model_EligibilityVerifier.is_auth_required - assert not model_EligibilityVerifier.uses_auth_verification + assert not model_EligibilityVerifier.uses_claims_verification @pytest.mark.django_db diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py index 37086ea1b6..8efbada346 100644 --- a/tests/pytest/eligibility/test_verify.py +++ b/tests/pytest/eligibility/test_verify.py @@ -52,12 +52,12 @@ def test_eligibility_from_api_no_verified_types( @pytest.mark.django_db -def test_eligibility_from_oauth_does_not_use_auth_verification( - mocked_session_verifier_does_not_use_auth_verification, model_TransitAgency +def test_eligibility_from_oauth_does_not_use_claims_verification( + mocked_session_verifier_does_not_use_claims_verification, model_TransitAgency ): - # mocked_session_verifier_does_not_use_auth_verification is Mocked version of the session.verifier() function + # mocked_session_verifier_does_not_use_claims_verification is Mocked version of the session.verifier() function # call it (with a None request) to return a verifier object - verifier = mocked_session_verifier_does_not_use_auth_verification(None) + verifier = mocked_session_verifier_does_not_use_claims_verification(None) types = eligibility_from_oauth(verifier, "claim", model_TransitAgency) @@ -65,11 +65,11 @@ def test_eligibility_from_oauth_does_not_use_auth_verification( @pytest.mark.django_db -def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_uses_auth_verification, model_TransitAgency): - # mocked_session_verifier_uses_auth_verification is Mocked version of the session.verifier() function +def test_eligibility_from_oauth_claim_mismatch(mocked_session_verifier_uses_claims_verification, model_TransitAgency): + # mocked_session_verifier_uses_claims_verification is Mocked version of the session.verifier() function # call it (with a None request) to return a verifier object - verifier = mocked_session_verifier_uses_auth_verification(None) - verifier.auth_claim = "claim" + verifier = mocked_session_verifier_uses_claims_verification(None) + verifier.claim = "claim" types = eligibility_from_oauth(verifier, "some_other_claim", model_TransitAgency) @@ -77,13 +77,13 @@ def test_eligibility_from_oauth_auth_claim_mismatch(mocked_session_verifier_uses @pytest.mark.django_db -def test_eligibility_from_oauth_auth_claim_match( - mocked_session_verifier_uses_auth_verification, model_EligibilityType, model_TransitAgency +def test_eligibility_from_oauth_claim_match( + mocked_session_verifier_uses_claims_verification, model_EligibilityType, model_TransitAgency ): - # mocked_session_verifier_uses_auth_verification is Mocked version of the session.verifier() function + # mocked_session_verifier_uses_claims_verification is Mocked version of the session.verifier() function # call it (with a None request) to return a verifier object - verifier = mocked_session_verifier_uses_auth_verification.return_value - verifier.auth_provider.claim = "claim" + verifier = mocked_session_verifier_uses_claims_verification.return_value + verifier.claims_provider.claim = "claim" verifier.eligibility_type = model_EligibilityType types = eligibility_from_oauth(verifier, "claim", model_TransitAgency) diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py index 6c05e2158f..904ee80fed 100644 --- a/tests/pytest/eligibility/test_views.py +++ b/tests/pytest/eligibility/test_views.py @@ -172,9 +172,9 @@ def test_index_calls_session_logout(client, session_logout_spy): @pytest.mark.django_db @pytest.mark.usefixtures( - "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_uses_auth_verification" + "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_uses_claims_verification" ) -def test_start_verifier_auth_required_logged_in(mocker, client): +def test_start_verifier_uses_claims_verification_logged_in(mocker, client): mock_session = mocker.patch("benefits.eligibility.views.session") mock_session.logged_in.return_value = True @@ -186,9 +186,9 @@ def test_start_verifier_auth_required_logged_in(mocker, client): @pytest.mark.django_db @pytest.mark.usefixtures( - "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_uses_auth_verification" + "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_uses_claims_verification" ) -def test_start_verifier_auth_required_not_logged_in(mocker, client): +def test_start_verifier_uses_claims_verification_not_logged_in(mocker, client): mock_session = mocker.patch("benefits.eligibility.views.session") mock_session.logged_in.return_value = False @@ -200,9 +200,9 @@ def test_start_verifier_auth_required_not_logged_in(mocker, client): @pytest.mark.django_db @pytest.mark.usefixtures( - "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_does_not_use_auth_verification" + "mocked_session_agency", "mocked_verifier_selection_form", "mocked_session_verifier_does_not_use_claims_verification" ) -def test_start_verifier_auth_not_required(client): +def test_start_verifier_does_not_use_claims_verification(client): path = reverse(ROUTE_START) response = client.get(path) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index ec34205a18..8d9d6082ba 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -839,7 +839,7 @@ def test_success_no_verifier(client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_session_eligibility") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_session_eligibility") def test_success_authentication_logged_in(mocker, client, model_TransitAgency, model_EligibilityType, mocked_analytics_module): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = True @@ -856,7 +856,7 @@ def test_success_authentication_logged_in(mocker, client, model_TransitAgency, m @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_session_eligibility") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_session_eligibility") def test_success_authentication_not_logged_in( mocker, client, model_TransitAgency, model_EligibilityType, mocked_analytics_module ): @@ -875,7 +875,7 @@ def test_success_authentication_not_logged_in( @pytest.mark.django_db @pytest.mark.usefixtures( - "mocked_session_agency", "mocked_session_verifier_does_not_use_auth_verification", "mocked_session_eligibility" + "mocked_session_agency", "mocked_session_verifier_does_not_use_claims_verification", "mocked_session_eligibility" ) def test_success_no_authentication(mocker, client, model_EligibilityType, mocked_analytics_module): mock_session = mocker.patch("benefits.enrollment.views.session") diff --git a/tests/pytest/oauth/test_analytics.py b/tests/pytest/oauth/test_analytics.py index 6737bccecb..f4b26ae0b0 100644 --- a/tests/pytest/oauth/test_analytics.py +++ b/tests/pytest/oauth/test_analytics.py @@ -4,28 +4,28 @@ @pytest.mark.django_db -def test_OAuthEvent_verifier_client_name_when_uses_auth_verification( - app_request, mocked_session_verifier_uses_auth_verification +def test_OAuthEvent_verifier_client_name_when_uses_claims_verification( + app_request, mocked_session_verifier_uses_claims_verification ): - mocked_verifier = mocked_session_verifier_uses_auth_verification(app_request) - mocked_verifier.auth_provider.client_name = "ClientName" + mocked_verifier = mocked_session_verifier_uses_claims_verification(app_request) + mocked_verifier.claims_provider.client_name = "ClientName" event = OAuthEvent(app_request, "event type") assert "auth_provider" in event.event_properties - assert event.event_properties["auth_provider"] == mocked_verifier.auth_provider.client_name + assert event.event_properties["auth_provider"] == mocked_verifier.claims_provider.client_name @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_does_not_use_auth_verification") -def test_OAuthEvent_verifier_no_client_name_when_does_not_use_auth_verification(app_request): +@pytest.mark.usefixtures("mocked_session_verifier_does_not_use_claims_verification") +def test_OAuthEvent_verifier_no_client_name_when_does_not_use_claims_verification(app_request): event = OAuthEvent(app_request, "event type") assert "auth_provider" not in event.event_properties @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_OAuthErrorEvent(app_request): event_default = OAuthErrorEvent(app_request, "the message", "the operation") diff --git a/tests/pytest/oauth/test_client.py b/tests/pytest/oauth/test_client.py index 3bfe63df89..d6b45c61d7 100644 --- a/tests/pytest/oauth/test_client.py +++ b/tests/pytest/oauth/test_client.py @@ -1,6 +1,6 @@ import pytest -from benefits.core.models import AuthProvider +from benefits.core.models import ClaimsProvider from benefits.oauth.client import _client_kwargs, _server_metadata_url, _authorize_params, _register_provider, create_client @@ -40,7 +40,7 @@ def test_authorize_params_no_scheme(): @pytest.mark.django_db def test_register_provider(mocker, mocked_oauth_registry): - mocked_client_provider = mocker.Mock(spec=AuthProvider) + mocked_client_provider = mocker.Mock(spec=ClaimsProvider) mocked_client_provider.client_name = "client_name_1" mocked_client_provider.client_id = "client_id_1" @@ -61,7 +61,7 @@ def test_register_provider(mocker, mocked_oauth_registry): @pytest.mark.django_db def test_create_client_already_registered(mocker, mocked_oauth_registry): - mocked_client_provider = mocker.Mock(spec=AuthProvider) + mocked_client_provider = mocker.Mock(spec=ClaimsProvider) mocked_client_provider.client_name = "client_name_1" mocked_client_provider.client_id = "client_id_1" @@ -73,7 +73,7 @@ def test_create_client_already_registered(mocker, mocked_oauth_registry): @pytest.mark.django_db def test_create_client_already_not_registered_yet(mocker, mocked_oauth_registry): - mocked_client_provider = mocker.Mock(spec=AuthProvider) + mocked_client_provider = mocker.Mock(spec=ClaimsProvider) mocked_client_provider.client_name = "client_name_1" mocked_client_provider.client_id = "client_id_1" diff --git a/tests/pytest/oauth/test_middleware_authverifier_required.py b/tests/pytest/oauth/test_middleware_authverifier_required.py index 31cf9b7e58..ec41069119 100644 --- a/tests/pytest/oauth/test_middleware_authverifier_required.py +++ b/tests/pytest/oauth/test_middleware_authverifier_required.py @@ -34,7 +34,7 @@ def test_authverifier_required_no_verifier(app_request, mocked_view, decorated_v @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_does_not_use_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_does_not_use_claims_verification") def test_authverifier_required_no_authverifier(app_request, mocked_view, decorated_view): response = decorated_view(app_request) @@ -49,15 +49,15 @@ def test_authverifier_required_misconfigured_verifier( app_request, mocked_view, decorated_view, - mocked_session_verifier_does_not_use_auth_verification, + mocked_session_verifier_does_not_use_claims_verification, mocked_analytics_module, mocked_sentry_sdk_module, api_url, form_class, ): # fake a misconfigured verifier - mocked_session_verifier_does_not_use_auth_verification.return_value.api_url = api_url - mocked_session_verifier_does_not_use_auth_verification.return_value.form_class = form_class + mocked_session_verifier_does_not_use_claims_verification.return_value.api_url = api_url + mocked_session_verifier_does_not_use_claims_verification.return_value.form_class = form_class response = decorated_view(app_request) diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py index adb0b5d5b2..80df6cfe87 100644 --- a/tests/pytest/oauth/test_views.py +++ b/tests/pytest/oauth/test_views.py @@ -49,17 +49,17 @@ def mocked_oauth_client_or_error_redirect__error(mocked_oauth_create_client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_oauth_client_or_error_redirect_no_oauth_client( app_request, - model_AuthProvider_with_verification, + model_ClaimsProvider_with_scope_and_claim, mocked_oauth_create_client, mocked_analytics_module, mocked_sentry_sdk_module, ): mocked_oauth_create_client.return_value = None - result = _oauth_client_or_error_redirect(app_request, model_AuthProvider_with_verification) + result = _oauth_client_or_error_redirect(app_request, model_ClaimsProvider_with_scope_and_claim) assert result.status_code == 302 assert result.url == reverse(ROUTE_SYSTEM_ERROR) @@ -68,11 +68,11 @@ def test_oauth_client_or_error_redirect_no_oauth_client( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_oauth_client_or_error_redirect__error") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_oauth_client_or_error_redirect__error") def test_oauth_client_or_error_redirect_oauth_client_exception( - app_request, model_AuthProvider_with_verification, mocked_analytics_module, mocked_sentry_sdk_module + app_request, model_ClaimsProvider_with_scope_and_claim, mocked_analytics_module, mocked_sentry_sdk_module ): - result = _oauth_client_or_error_redirect(app_request, model_AuthProvider_with_verification) + result = _oauth_client_or_error_redirect(app_request, model_ClaimsProvider_with_scope_and_claim) assert result.status_code == 302 assert result.url == reverse(ROUTE_SYSTEM_ERROR) @@ -81,11 +81,11 @@ def test_oauth_client_or_error_redirect_oauth_client_exception( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_oauth_create_client") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_oauth_create_client") def test_oauth_client_or_error_oauth_client( - app_request, model_AuthProvider_with_verification, mocked_analytics_module, mocked_sentry_sdk_module + app_request, model_ClaimsProvider_with_scope_and_claim, mocked_analytics_module, mocked_sentry_sdk_module ): - result = _oauth_client_or_error_redirect(app_request, model_AuthProvider_with_verification) + result = _oauth_client_or_error_redirect(app_request, model_ClaimsProvider_with_scope_and_claim) assert hasattr(result, "authorize_redirect") mocked_analytics_module.error.assert_not_called() @@ -93,7 +93,7 @@ def test_oauth_client_or_error_oauth_client( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_oauth_client_or_error_redirect__error") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_oauth_client_or_error_redirect__error") def test_login_oauth_client_init_error(app_request, mocked_analytics_module): result = login(app_request) @@ -111,7 +111,7 @@ def test_login_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_login(app_request, mocked_oauth_client_or_error_redirect__client, mocked_analytics_module): assert not session.logged_in(app_request) mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value @@ -126,7 +126,7 @@ def test_login(app_request, mocked_oauth_client_or_error_redirect__client, mocke @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_login_authorize_redirect_exception( app_request, mocked_oauth_client_or_error_redirect__client, mocked_analytics_module, mocked_sentry_sdk_module ): @@ -142,7 +142,7 @@ def test_login_authorize_redirect_exception( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") @pytest.mark.parametrize("status_code", [400, 401, 403, 404, 500, 501, 503]) def test_login_authorize_redirect_error_response( app_request, mocked_oauth_client_or_error_redirect__client, mocked_analytics_module, mocked_sentry_sdk_module, status_code @@ -159,7 +159,7 @@ def test_login_authorize_redirect_error_response( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_oauth_client_or_error_redirect__error") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_oauth_client_or_error_redirect__error") def test_authorize_oauth_client_init_error(app_request, mocked_analytics_module, mocked_sentry_sdk_module): result = authorize(app_request) @@ -178,7 +178,7 @@ def test_authorize_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_authorize_error( mocked_oauth_client_or_error_redirect__client, mocked_analytics_module, mocked_sentry_sdk_module, app_request ): @@ -198,7 +198,7 @@ def test_authorize_error( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_authorize_empty_token( mocked_oauth_client_or_error_redirect__client, mocked_analytics_module, mocked_sentry_sdk_module, app_request ): @@ -218,7 +218,7 @@ def test_authorize_empty_token( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_authorize_success(mocked_oauth_client_or_error_redirect__client, mocked_analytics_module, app_request): mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token"} @@ -236,10 +236,10 @@ def test_authorize_success(mocked_oauth_client_or_error_redirect__client, mocked @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_with_claim_true( - app_request, mocked_session_verifier_uses_auth_verification, mocked_oauth_client_or_error_redirect__client + app_request, mocked_session_verifier_uses_claims_verification, mocked_oauth_client_or_error_redirect__client ): - verifier = mocked_session_verifier_uses_auth_verification.return_value - verifier.auth_provider.claim = "claim" + verifier = mocked_session_verifier_uses_claims_verification.return_value + verifier.claims_provider.claim = "claim" mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "1"}} @@ -254,10 +254,10 @@ def test_authorize_success_with_claim_true( @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_with_claim_false( - app_request, mocked_session_verifier_uses_auth_verification, mocked_oauth_client_or_error_redirect__client + app_request, mocked_session_verifier_uses_claims_verification, mocked_oauth_client_or_error_redirect__client ): - verifier = mocked_session_verifier_uses_auth_verification.return_value - verifier.auth_provider.claim = "claim" + verifier = mocked_session_verifier_uses_claims_verification.return_value + verifier.claims_provider.claim = "claim" mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "0"}} @@ -272,12 +272,12 @@ def test_authorize_success_with_claim_false( @pytest.mark.django_db def test_authorize_success_with_claim_error( app_request, - mocked_session_verifier_uses_auth_verification, + mocked_session_verifier_uses_claims_verification, mocked_oauth_client_or_error_redirect__client, mocked_analytics_module, ): - verifier = mocked_session_verifier_uses_auth_verification.return_value - verifier.auth_provider.claim = "claim" + verifier = mocked_session_verifier_uses_claims_verification.return_value + verifier.claims_provider.claim = "claim" mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "10"}} @@ -293,10 +293,10 @@ def test_authorize_success_with_claim_error( @pytest.mark.django_db @pytest.mark.usefixtures("mocked_analytics_module") def test_authorize_success_without_verifier_claim( - app_request, mocked_session_verifier_uses_auth_verification, mocked_oauth_client_or_error_redirect__client + app_request, mocked_session_verifier_uses_claims_verification, mocked_oauth_client_or_error_redirect__client ): - verifier = mocked_session_verifier_uses_auth_verification.return_value - verifier.auth_provider.claim = "" + verifier = mocked_session_verifier_uses_claims_verification.return_value + verifier.claims_provider.claim = "" mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "True"}} @@ -318,12 +318,12 @@ def test_authorize_success_without_verifier_claim( ) def test_authorize_success_without_claim_in_response( app_request, - mocked_session_verifier_uses_auth_verification, + mocked_session_verifier_uses_claims_verification, mocked_oauth_client_or_error_redirect__client, access_token_response, ): - verifier = mocked_session_verifier_uses_auth_verification.return_value - verifier.auth_provider.claim = "claim" + verifier = mocked_session_verifier_uses_claims_verification.return_value + verifier.claims_provider.claim = "claim" mocked_oauth_client = mocked_oauth_client_or_error_redirect__client.return_value mocked_oauth_client.authorize_access_token.return_value = access_token_response @@ -336,7 +336,7 @@ def test_authorize_success_without_claim_in_response( @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_cancel(app_request, mocked_analytics_module): unverified_route = reverse(ROUTE_UNVERIFIED) @@ -356,7 +356,7 @@ def test_cancel_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_oauth_client_or_error_redirect__error") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification", "mocked_oauth_client_or_error_redirect__error") def test_logout_oauth_client_init_error(app_request): result = authorize(app_request) @@ -373,7 +373,7 @@ def test_logout_no_session_verifier(app_request): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_logout(app_request, mocker, mocked_oauth_client_or_error_redirect__client, mocked_analytics_module): # logout internally calls deauthorize_redirect # this mocks that function and a success response @@ -400,7 +400,7 @@ def test_logout(app_request, mocker, mocked_oauth_client_or_error_redirect__clie @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") +@pytest.mark.usefixtures("mocked_session_verifier_uses_claims_verification") def test_post_logout(app_request, mocked_analytics_module): origin = reverse(ROUTE_INDEX) session.update(app_request, origin=origin)