Package: inuits-policy-based-auth
inuits_policy_based_authinuits_policy_based_auth is a python package for securing API endpoints based on policies.InstallationInstall inuits_policy_based_auth as follows:pip install inuits-policy-based-authGetting StartedInstantiate the PolicyFactory in you app, for example in app.py (all examples given are based on a Python Flask app).frominuits_policy_based_authimportPolicyFactorypolicy_factory=PolicyFactory()Manually loading policiesImporting and registering policies can be done manually. Don't forget to set a fallback key, this ensures that policies of a specific app are applied when the app cannot be determined automatically.frominuits_policy_based_auth.authentication.policies.token_based_policies.authlib_flask_oauth2_policyimport(AuthlibFlaskOauth2Policy,)frominuits_policy_based_auth.authorization.policies.super_admin_policyimport(SuperAdminPolicy,)policy_factory.register_authentication_policy("apps.[app_name]",AuthlibFlaskOauth2Policy(...))policy_factory.register_authorization_policy("apps.[app_name]",SuperAdminPolicy())policy_factory.set_fallback_key_for_policy_mapping("apps.[app_name]")However, it is recommended to load policies dynamically as this will allow you to make use of the full potential of this package.Dynamically loading policiesYou can write a loader which loads policies dynamically based on a configuration file.Example configuration file:{"[app_name_1]":{"policies":{"authentication":["token_based_policies.authlib_flask_oauth2_policy","token_based_policies.tenant_token_roles_policy"],"authorization":["super_admin_policy","scope_based_policy"]}},"[app_name_2]":{"policies":{"authentication":["token_based_policies.authlib_flask_oauth2_policy","token_based_policies.tenant_token_roles_policy"],"authorization":["super_admin_policy","open_data_policy"]}}}Example policy_loader.py:importjsonimportosfromimportlibimportimport_modulefrominuits_policy_based_authimportPolicyFactoryfrominuits_policy_based_auth.exceptionsimport(PolicyFactoryException,)fromloggingimportLoggerdefload_policies(policy_factory:PolicyFactory,logger:Logger):apps={}withopen(str(os.getenv("[CONFIGURATION_FILE_NAME]")),"r")asconfiguration_file:apps=json.load(configuration_file)forappinapps:try:auth_type="authentication"forpolicy_module_nameinapps[app]["policies"].get(auth_type):policy=__get_class(app,auth_type,policy_module_name)policy=__instantiate_authentication_policy(policy_module_name,policy,logger)policy_factory.register_authentication_policy(f"apps.{app}",policy)auth_type="authorization"forpolicy_module_nameinapps[app]["policies"].get(auth_type):policy=__get_class(app,auth_type,policy_module_name)policy_factory.register_authorization_policy(f"apps.{app}",policy())exceptExceptionaserror:raisePolicyFactoryException(f"Policy factory was not configured correctly:{str(error)}").with_traceback(error.__traceback__)policy_factory.set_fallback_key_for_policy_mapping("apps.[app_name]")def__get_class(app,auth_type,policy_module_name):locations=[policy_module_name,f"apps.{app}.policies.{auth_type}.{policy_module_name}",f"inuits_policy_based_auth.{auth_type}.policies.{policy_module_name}",]forlocationinlocations:try:module=import_module(location)breakexceptModuleNotFoundError:passelse:raiseModuleNotFoundError(f"Policy{policy_module_name}not found")policy_class_name=module.__name__.split(".")[-1].title().replace("_","")policy=getattr(module,policy_class_name)returnpolicydef__instantiate_authentication_policy(policy_module_name,policy,logger:Logger):token_schema=__load_token_schema()ifpolicy_module_name=="token_based_policies.authlib_flask_oauth2_policy":allow_anonymous_users=(Trueifos.getenv("ALLOW_ANONYMOUS_USERS","false").lower()=="true"elseFalse)returnpolicy(logger,token_schema,os.getenv("STATIC_ISSUER"),os.getenv("STATIC_PUBLIC_KEY"),os.getenv("ALLOWED_ISSUERS"),allow_anonymous_users,)ifpolicy_module_name=="token_based_policies.tenant_token_roles_policy":returnpolicy(token_schema,os.getenv("ROLE_SCOPE_MAPPING",os.getenv("API_SCOPES")),Trueifos.getenv("ALLOW_ANONYMOUS_USERS","false").lower()=="true"elseFalse,)returnpolicy()def__load_token_schema()->dict:token_schema_path=os.getenv("TOKEN_SCHEMA","path/to/token_schema.json")withopen(token_schema_path,"r")astoken_schema:returnjson.load(token_schema)Now you can import the loader in app.py and passpolicy_factoryas an argument to it.fromapps.policy_loaderimportload_policiesfromloggingimportLoggerload_policies(policy_factory,Logger(""))As you can see in these examples, dynamically loading policies will allow you to add new policies and override existing ones, which makes this package highly customizable and generic.Custom policiesContinuing from the examples above, you can make a custom authorization policy by creating a folderpolicieswithin a specific app. Here you should create the foldersauthenticationandauthorizationthat will contain custom policies which you can add to your configuration. In this case we name our new policy the same as an existing one, which will override it. Each authentication policy must inherit from BaseAuthenticationPolicy and implement the abstract methodauthenticate, while each authorization policy must inherit from BaseAuthorizationPolicy and implement the abstract methodauthorize.Example folder structure:api
├── apps
│  ├── [app_name]
│  │  ├── policies
│  │  │  ├── authentication
│  │  │  └── authorization
│  │  │     └── open_data_policy.py
│  │  ├── ...
...
│  ├── configuration.json
│  └── policy_loader.py
...
└── app.pyExample custom open_data_policy.py:frominuits_policy_based_authimportBaseAuthorizationPolicyclassOpenDataPolicy(BaseAuthorizationPolicy):defauthorize(self,policy_context,_,request_context):request=request_context.http_requestifrequest.method=="GET":policy_context.access_verdict=Truereturnpolicy_contextUsageIf everything is set up correctly, you can use theapply_policiesdecorator as follows:fromappimportpolicy_factoryfromflaskimportrequestfrominuits_policy_based_authimportRequestContextclassEntity():@policy_factory.apply_policies(RequestContext(request,["[scope_1]","[scope_2]"]))defget(self):...You can also use theauthenticatedecorator to only apply authentication policies:fromappimportpolicy_factoryfromflaskimportrequestfrominuits_policy_based_authimportRequestContextclassEntity():@policy_factory.authenticate(RequestContext(request))defget(self):...ContributingDo not hesitate to open issues and create pull requests.