From b0a6151e72dbf7ffcecc7fb167d438a58b1d9cec Mon Sep 17 00:00:00 2001 From: Madhumita Date: Tue, 20 Dec 2022 14:41:39 +0530 Subject: [PATCH] fix: #2201 --- docs/admin/developer/interception-scripts.md | 10 +- .../developer/scripts/consent-gathering.md | 10 +- .../person-authentication-interface.md | 211 +++++++------- .../scripts/person-authentication.md | 89 +++--- docs/admin/developer/scripts/update-token.md | 261 +++++++++++++++++- .../{sample-script => }/README.md | 0 .../{sample-script => }/sample_script.py | 0 7 files changed, 426 insertions(+), 155 deletions(-) rename docs/script-catalog/update_token/{sample-script => }/README.md (100%) rename docs/script-catalog/update_token/{sample-script => }/sample_script.py (100%) diff --git a/docs/admin/developer/interception-scripts.md b/docs/admin/developer/interception-scripts.md index 5a4efd28dc7..b7b669993c0 100644 --- a/docs/admin/developer/interception-scripts.md +++ b/docs/admin/developer/interception-scripts.md @@ -31,14 +31,14 @@ request authorization for each scope, and display the respective scope descripti 1. [Dynamic Scopes](./scripts/dynamic-scope.md) : Enables admin to generate scopes on the fly, for example by calling external APIs 1. ID Generator +1. [Update Token][Inrospection](./scripts/update-token) 1. Session Management 1. SCIM -1. Inrospection +1. [Inrospection](./scripts/introspection) 1. Resource Owner Password Credentials 1. UMA 2 RPT Authorization Policies 1. UMA 2 Claims-Gathering - ## Implementation languages - Jython or pure Java Interception scripts are written in **[Jython](http://www.jython.org/)** or in @@ -195,7 +195,9 @@ where is the name of the library to install. ### Debugging a Jython script -This [article](../interception-scripts-debug) covers the details. +1. This [article](../interception-scripts-debug-ce) covers the details for debugging a script in a developer environment (CE). + +2. This [article](../interception-scripts-debug) covers the details for debugging a script in a CN environment. *** @@ -256,7 +258,7 @@ The post-config-scripts and put-config-scripts require various details about the ### Basic schema of a custom script Command: -`/opt/jans/jans-cli/config-cli.py --schema CustomScript` +`/opt/jans/jans-cli/config-cli.py --schema /components/schemas/CustomScript ` Output: ```json diff --git a/docs/admin/developer/scripts/consent-gathering.md b/docs/admin/developer/scripts/consent-gathering.md index 637ba9b6a57..5f86ea472ce 100644 --- a/docs/admin/developer/scripts/consent-gathering.md +++ b/docs/admin/developer/scripts/consent-gathering.md @@ -3,10 +3,12 @@ tags: - administration - developer - scripts + - ConsentGathering + - ConsentGatheringType --- ## Overview -OAuth 2.0 allows providers to prompt users for consent before releasing their personal information to a client (application). The standard consent process is binary: approve or deny. Using the consent gathering interception script, the consent flow can be customized to meet unique business requirements, for instance to support payment authorization, where you need to present transactional information, or where you need to step-up authentication to add security. +OAuth 2.0 allows providers to prompt users for consent before releasing their personal information to a client (application). The standard consent process is binary: approve or deny. Using the consent gathering interception script, the consent flow can be customized to meet unique business requirements, for instance to support payment authorization, where you need to present transactional information, or where you need to step-up authentication to add security. ## Interface The consent gathering script implements the [ConsentGathering](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/authz/ConsentGatheringType.java) interface. This extends methods from the base script type in addition to adding new methods: @@ -65,7 +67,7 @@ class ConsentGathering(ConsentGatheringType): return 11 # All user entered values can be access via Map context.getPageAttributes() - def authorize(self, step, context): + def authorize(self, step, context): print "Consent-Gathering. Authorizing..." if step == 1: @@ -95,7 +97,7 @@ class ConsentGathering(ConsentGatheringType): if step == 2: pageAttributes = context.getPageAttributes() - + # Generate random consent gathering request consentRequest = "Requested transaction #%s approval for the amount of sum $ %s.00" % ( random.randint(100000, 1000000), random.randint(1, 100) ) pageAttributes.put("consent_request", consentRequest) @@ -216,4 +218,4 @@ public class ConsentGathering implements ConsentGatheringType { return ""; } } -``` \ No newline at end of file +``` diff --git a/docs/admin/developer/scripts/person-authentication-interface.md b/docs/admin/developer/scripts/person-authentication-interface.md index 048abebd5dd..c14500906e9 100644 --- a/docs/admin/developer/scripts/person-authentication-interface.md +++ b/docs/admin/developer/scripts/person-authentication-interface.md @@ -5,113 +5,128 @@ tags: - scripts --- - - ## Person Authentication interface The **[PersonAuthenticationType](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/auth/PersonAuthenticationType.java)** script is described by a java interface whose methods should be overridden to implement an authentication workflow. -### Methods to override: -1. `init(self, customScript, configurationAttributes)` : This method is only called once during the script initialization (or jans-auth service restarts). It can be used for global script initialization, initiate objects etc. - ``` +### Inherited Methods +| Method header | Method description | +|:-----|:------| +| `def init(self, customScript, configurationAttributes)` | This method is only called once during the script initialization. It can be used for global script initialization, initiate objects etc | +| `def destroy(self, configurationAttributes)` | This method is called once to destroy events. It can be used to free resource and objects created in the `init()` method | +| `def getApiVersion(self, configurationAttributes, customScript)` | The getApiVersion method allows API changes in order to do transparent migration from an old script to a new API. Only include the customScript variable if the value for getApiVersion is greater than 10 | + +#### Objects +| Object name | Object description | +|:-----|:------| +|`customScript`| The custom script object. [Reference](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/model/CustomScript.java) | +|`configurationAttributes`| `configurationProperties` passed in when adding custom script. `Map configurationAttributes` | +|`SimpleCustomProperty`| Map of configuration properties. [Reference](https://github.com/JanssenProject/jans/blob/main/jans-core/util/src/main/java/io/jans/model/SimpleCustomProperty.java) | + +Pseudo code: +``` def init(self, customScript, configurationAttributes): # an example of initializing global variables from script configuration if configurationAttributes.containsKey("registration_uri"): self.registrationUri = configurationAttributes.get("registration_uri").getValue2() return True - ``` +``` -2. `destroy(self, configurationAttributes)` : This method is called when a custom script fails to initialize or upon jans-auth service restarts. It can be used to free resource and objects created in the init() method - ``` +``` def destroy(self, configurationAttributes): - print "OTP. Destroy" + print "ACR_NAME. Destroy" # cleanup code here return True - ``` - -3. ` authenticate(self, configurationAttributes, requestParameters, step)` : The most important method which will encapsulate the logic for user credential verification / validation - ``` - def authenticate(self, configurationAttributes, requestParameters, step): - authenticationService = CdiUtil.bean(AuthenticationService) - - if (step == 1): - # 1. obtain user name and password from UI - # 2. verify if entry exists in database - # 3. authenticationService.authenticate(user_name, user_password) - # 4. return True or False - - elif step == 2: - # 1. obtain credentials from UI - # 2. validate the credentials - # 3. return True or False - ``` - - -4. `prepareForStep(self, configurationAttributes, requestParameters, step)` : This method can be used to prepare variables needed to render the UI page and store them in a suitable context. - - ``` - prepareForStep(self, configurationAttributes, requestParameters, step) - # if (step == 1): - # do this - # if (step == 2) - # do something else - # 2. return True or False - ``` - -5. `getExtraParametersForStep` : Used to save session variables between steps. The Jans-auth Server persists these variables to support stateless, two-step authentications even in a clustered environment. - ``` - def getExtraParametersForStep(self, configurationAttributes, step): - return Arrays.asList("paramName1", "paramName2", "paramName3") - ``` -6. `getCountAuthenticationSteps`: This method normally just returns 1, 2, or 3. In some cases, depending on the context like based on the user's country or department, you can decide to go for multistep or single step authentication. - ``` - def getCountAuthenticationSteps(self, configurationAttributes): - return 1 - ``` -7. `getPageForStep`: Used to specify the UI page you want to show for a given step. - ``` - def getPageForStep(self, configurationAttributes, step): - # Used to specify the page you want to return for a given step - if (step == 1): - return "/auth/login.xhtml" - if (step == 2) - return "/auth/enterOTP.xhtml" - ``` -8. `getNextStep` : Steps usually go incrementally as 1, 2, 3... unless you specify a case where it can be reset to a previous step, or skip a particular step based on business case. - ``` - def getNextStep(self, configurationAttributes, requestParameters, step): - # steps usually are incremented 1, 2, 3... unless you specify a case where it can be reset to a previous step, or skip a particular step based on - business case. - return -1 - ``` -9. `getAuthenticationMethodClaims` : Array of strings that are identifiers for authentication methods used in the authentication. In OpenID Connect, if the identity provider supplies an "amr" claim in the ID Token resulting from a successful authentication, the relying party can inspect the values returned and thereby learn details about how the authentication was performed. - ``` - def getAuthenticationMethodClaims(self, requestParameters): - return Arrays.asList("pwd", "otp") - ``` -10. `getApiVersion` : This value is currently meant to be hardcoded to 11 - - ``` - def getApiVersion(self): - return 11 - ``` -11. `isValidAuthenticationMethod` : This method is used to check if the authentication method is in a valid state. For example we can check there if a 3rd party mechanism is available to authenticate users. As a result it should either return True or False. - ``` - def isValidAuthenticationMethod(self, usageType, configurationAttributes): - return True - ``` -12. `getAlternativeAuthenticationMethod` : This method is called only if the current authentication method is in an invalid state. Hence authenticator calls it only if isValidAuthenticationMethod returns False. As a result it should return the reserved authentication method name. - ``` - def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes): - return None - ``` - -13. `getLogoutExternalUrl` : Returns the 3rd-party URL that is used to end session routines. The control from this Third party URL should re-direct user back to /oxauth/logout.htm again with empty URL query string. Jans-Auth server will then continue of the extended logout flow, restore the original URL query string, and send user to `/jans-auth/end_session` to complete it. - ``` - def getLogoutExternalUrl(self, configurationAttributes, requestParameters): - return None - ``` -14. `logout` : This method is not mandatory. It can be used in cases when you need to execute specific logout logic in the authentication script when jans-auth receives an end session request to the /oxauth/logout.htm endpoint (which receives the same set of parameters than the usual end_session endpoint). This method should return True or False; when False jans-auth stops processing the end session request workflow. - ``` - def logout(self, configurationAttributes, requestParameters): - return True - ``` \ No newline at end of file +``` + +``` + def getApiVersion(self): + return 11 +``` + +### Person Authentication interface Methods +| | Method header | Method description | +:-----|:-----|:------| +|1.|`prepareForStep(self, configurationAttributes, requestParameters, step)` | This method can be used to prepare variables needed to render the UI page and store them in a suitable context.| +|2.|` authenticate(self, configurationAttributes, requestParameters, step)` | The most important method which will encapsulate the logic for user credential verification / validation| +|3.| `getExtraParametersForStep` | Used to save session variables between steps. The Jans-auth Server persists these variables to support stateless, two-step authentications even in a clustered environment.| +|4.|`getCountAuthenticationSteps`| This method normally just returns 1, 2, or 3. In some cases, depending on the context like based on the user's country or department, you can decide to go for multistep or single step authentication.| +|5.| `getPageForStep`| Used to specify the UI page you want to show for a given step.| +|6.| `getNextStep` | Steps usually go incrementally as 1, 2, 3... unless you specify a case where it can be reset to a previous step, or skip a particular step based on business case.| +|7.| `getAuthenticationMethodClaims` | Array of strings that are identifiers for authentication methods used in the authentication. In OpenID Connect, if the identity provider supplies an "amr" claim in the ID Token resulting from a successful authentication, the relying party can inspect the values returned and thereby learn details about how the authentication was performed.| +|8.| `isValidAuthenticationMethod`| This method is used to check if the authentication method is in a valid state. For example we can check there if a 3rd party mechanism is available to authenticate users. As a result it should either return True or False.| +|9.| `getAlternativeAuthenticationMethod` | This method is called only if the current authentication method is in an invalid state. Hence authenticator calls it only if isValidAuthenticationMethod returns False. As a result it should return the reserved authentication method name.| +|10. `getLogoutExternalUrl` | Returns the 3rd-party URL that is used to end session routines. The control from this Third party URL should re-direct user back to /oxauth/logout.htm again with empty URL query string. Jans-Auth server will then continue of the extended logout flow, restore the original URL query string, and send user to `/jans-auth/end_session` to complete it.| +|11. `logout` | This method is not mandatory. It can be used in cases when you need to execute specific logout logic in the authentication script when jans-auth receives an end session request to the /oxauth/logout.htm endpoint (which receives the same set of parameters than the usual end_session endpoint). This method should return True or False; when False jans-auth stops processing the end session request workflow.| + +#### Objects +| Object name | Object description | +|:-----|:------| +|`configurationAttributes`| `configurationProperties` passed in when adding custom script. `Map configurationAttributes` | +|`SimpleCustomProperty`| Map of configuration properties. [Reference](https://github.com/JanssenProject/jans/blob/main/jans-core/util/src/main/java/io/jans/model/SimpleCustomProperty.java) | +|usageType|[AuthenticationScriptUsageType](https://github.com/JanssenProject/jans/blob/main/jans-core/util/src/main/java/io/jans/model/AuthenticationScriptUsageType.java)| +|step|Integer indicating step number| +|step|Integer indicating step number| +|requestParameters|Request parameters stored as Map| + + +Pseudocode: +``` +def prepareForStep(self, configurationAttributes, requestParameters, step): + if (step == 1): + # do this + if (step == 2): + # do something else + return True # or False + +def authenticate(self, configurationAttributes, requestParameters, step): + authenticationService = CdiUtil.bean(AuthenticationService) + + if (step == 1): + # 1. obtain user name and password from UI + # 2. verify if entry exists in database + # 3. authenticationService.authenticate(user_name, user_password) + # 4. return True or False + + elif step == 2: + # 1. obtain credentials from UI + # 2. validate the credentials + # 3. return True or False + +def getExtraParametersForStep(self, configurationAttributes, step): + return Arrays.asList("paramName1", "paramName2", "paramName3") + +def getCountAuthenticationSteps(self, configurationAttributes): + return 1 + +def getPageForStep(self, configurationAttributes, step): + # Used to specify the page you want to return for a given step + if (step == 1): + return "/auth/login.xhtml" + if (step == 2) + return "/auth/enterOTP.xhtml" + + def getNextStep(self, configurationAttributes, requestParameters, step): +# steps usually are incremented 1, 2, 3... unless you specify a case where it can be reset to a previous step, or skip a particular step based on business case. + return -1 + + def getAuthenticationMethodClaims(self, requestParameters): + return Arrays.asList("pwd", "otp") + + def isValidAuthenticationMethod(self, usageType, configurationAttributes): + return True + + def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes): + return None + + def getLogoutExternalUrl(self, configurationAttributes, requestParameters): + return None + + def logout(self, configurationAttributes, requestParameters): + return True + +``` +### Example scripts: + +1. [Basic script](https://github.com/JanssenProject/jans/tree/main/docs/script-catalog/person_authentication/basic-external-authenticator) + +2. [Script catalog](https://github.com/JanssenProject/jans/tree/main/docs/script-catalog) diff --git a/docs/admin/developer/scripts/person-authentication.md b/docs/admin/developer/scripts/person-authentication.md index 1aae0f69844..7477e861295 100644 --- a/docs/admin/developer/scripts/person-authentication.md +++ b/docs/admin/developer/scripts/person-authentication.md @@ -3,69 +3,84 @@ tags: - administration - developer - scripts + - acr_values_supported + - 2FA + - PersonAuthenticationType + - acr + - weld + - + --- -# Person Authentication scripts -The Jans-Auth Server leverages interception scripts of [PersonAuthenticationType](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/auth/PersonAuthenticationType.java) which when implemented can facilitate complex multi-step, multi-factor authentication workflows. The authentication flow in the Jans Server is driven by the openID spec. The authorization request to the OP (Jans server) contains an optional query parameter called `acr_values` which is used by the OP to pick an interception script which will be run when `/authorize` endpoint (Authentication flow) is invoked. The name of each script corresponds with its `acr` value in the Jans-Auth Server. +## Person Authentication scripts +The Jans-Auth Server leverages interception scripts of [PersonAuthenticationType](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/auth/PersonAuthenticationType.java) which when implemented can facilitate complex multi-step, multi-factor authentication workflows. + +The authentication flow in the Jans Server is driven by the openID spec. The authorization request to the OP (Jans server) contains an optional query parameter called `acr_values` which is used by the OP to pick an interception script which will be run when `/authorize` endpoint (Authentication flow) is invoked. + +Each authentication method, whose name is the `acr` value, is tied to a `PersonAuthenticationType` script which offers the authentication workflow. Typically, a `PersonAuthenticationType` script can be used to: 1. introduce a new 2FA authentication mechanism - 2. customise multistep authentication + 2. customize multistep authentication 3. offer Social logins - 4. proactively perform fraud detection and block the user. + 4. proactively perform fraud detection and block a fraudulent user. -## Default authentication method: -In an OpenID Connect authentication request, one of the optional parameters defined is `acr_values`. This is the primary way for a client to signal to the OpenID Provider (OP) the preferred way to authenticate the subject. -A client may also specify `default_acr_values` during registration (and omit the parameter while making an authentication request). **In the Jansssen Server configuration, `acr` is used to name the authentication workflow.** +Authentication mechanisms offered by Jans can be confirmed by checking the Janssen OP configuration URL, `https:///.well-known/openid-configuration`, and finding the `acr_values_supported`. -`default_acr`: This is the default authentication mechanism exposed to all applications that send users to the Janssen Server for sign-in. Unless an app specifically requests a different form of authentication using the OpenID Connect acr_values parameter (as specified below), users will receive the form of authentication specified in this field. +## Building blocks of an authentication workflow -If a default ACR is not specified, Janssen will determine it based on enabled scripts and the internal user/password ACR. This internal ACR, `simple_password_auth`, is set to level -1. This means that it has lower priority than any scripts, so Janssen server will use it only if no other authentication method is set. +Jans-auth server comprises of a number of beans, configuration files and Facelets (JSF) views, packaged as a WAR module. That means custom scripts and custom pages (JSF facelets) can make use of business logic already encapsulated in the Weld managed beans. The following sections explain how authentication flow can be built using a custom script. -Use the jans-cli to [update / look-up the default authentication method](https://github.com/JanssenProject/jans-cli/edit/main/docs/cli/cli-default-authentication-method.md). +### A. Custom script +The **PersonAuthenticationType** script is described by a java interface whose methods should be overridden to implement an authentication workflow. +The [article](person-authentication-interface) talks about these methods in detail and the psuedo code for each method. -## Multiple Authentication Mechanisms -The Jans Server can concurrently support multiple authentication mechanisms, enabling Web and mobile apps (clients) to request a specific type of authentication using the standard OpenID Connect request parameter: acr_value. +### B. UI pages: +All web pages are **xhtml** files. The Jans-auth server comes with a default set of pages for login, logout, errors, authorizations. You can easily override these pages or write new ones. You can easily apply your own stylesheet, images and resouce-bundles to your pages. -### Enabling an authentication mechanism -By default, users will get the default authentication mechanism as specified above. However, **using the OpenID Connect acr_values parameter, web and mobile clients can request any enabled authentication mechanism**. +This [article](../../customization/customize-web-pages) covers all the details you need to write your own web page. -1. Obtain the json contents of a custom script by using a jans-cli command like `get-config-scripts-by-type`, `get-config-scripts-by-inum` etc. - Example : - - `/opt/jans/jans-cli/config-cli.py --operation-id get-config-scripts-by-type --url-suffix type:PERSON_AUTHENTICATION` - - `/opt/jans/jans-cli/config-cli.py --operation-id get-config-scripts-by-inum --url-suffix inum:6122281b-b55d-4dd0-8115-b098eeeee2b7` +### C. Business logic in Custom script: +Jans-auth server uses Weld 3.0 (JSR-365 aka CDI 2.0) for managed beans. The most important aspects of business logic are implemented through a set of beans. Details and examples of this can be found in this [article](../managed-beans.md) -2. [Update the custom script](https://github.com/JanssenProject/jans-cli/blob/main/docs/cli/cli-custom-scripts.md#update-an-existing-custom-script) and change the `enabled` attribute to `true` +### D. Third party libraries for use in the custom script +Java or Python libraries to be imported and used very easily. Remember, you can import a python library only if it has been written in "pure python". +More details of this mentioned [here](../interception-scripts.md#using-python-libraries-in-a-script) -### Level (rank) of an Authentication mechanism : -Each authentication mechanism has a "Level" assigned to it which describes how secure and strict it is. **The higher the "Level", the more reliable mechanism represented by the script is.** Though several mechanisms can be enabled at the same Janssen server instance at the same time, for any specific user's session only one of them can be set as the current one (and will be returned as `acr` claim of id_token for them). If after initial session is created a new authorization request from a RP comes in specifying another authentication method, its "Level" will be compared to that of the method currently associated with this session. If requested method's "Level" is lower or equal to it, nothing is changed and the usual SSO behavior is observed. If it's higher (i.e. a more secure method is requested), it's not possible to serve such request using the existing session's context, and user must re-authenticate themselves to continue. If they succeed, a new session becomes associated with that requested mechanism instead. +### E. Configuring the `acr` parameter in the Jans-auth server: +The `acr` parameter can be configured in the following ways : +#### 1. Default authentication method: -Enabled scripts can be confirmed by checking the Janssen OP configuration URL, `https:///.well-known/openid-configuration`, and finding the "**acr_values_supported**". +`default_acr`: This is the default authentication mechanism exposed to all applications that send users to the Janssen Server for sign-in. Unless an app specifically requests a different form of authentication using the OpenID Connect acr_values parameter (as specified below), users will receive the form of authentication specified in this field. -Learn more about acr_values in the [OpenID Connect core spec](http://openid.net/specs/openid-connect-core-1_0.html#acrSemantics). +#### Internal ACR +If a default ACR is not specified, Janssen will determine it based on enabled scripts and the internal user/password ACR. This internal ACR, `simple_password_auth`, is set to level -1. This means that it has lower priority than any scripts, so Janssen server will use it only if no other authentication method is set. +Use the jans-cli to [update / look-up the default authentication method](https://github.com/JanssenProject/jans-cli/edit/main/docs/cli/cli-default-authentication-method.md). -## Building blocks of an authentication workflow +#### Authentication method for a client (RP): +A client may also specify `default_acr_values` during registration (and omit the parameter `acr_values` while making an authentication request). -Jans-auth comprises of a number of beans, configuration files and Facelets (JSF) views, packaged as a WAR module. That means custom scripts and custom pages (JSF facelets) can make use of business logic already encapsulated in the Weld managed beans. The following sections explain how authentication flow can be built using a custom script. +#### Multiple Authentication Mechanisms +The Jans Server can concurrently support multiple authentication mechanisms, enabling Web and mobile apps (clients) to request a specific type of authentication using the standard OpenID Connect request parameter: `acr_values`. +Learn more about acr_values in the [OpenID Connect core spec](http://openid.net/specs/openid-connect-core-1_0.html#acrSemantics). -### A. Writing a custom script -The **PersonAuthenticationType** script is described by a java interface whose methods should be overridden to implement an authentication workflow. -The [article](./person-authentication-interface) talks about these methods in detail and the psuedo code in each method will help drive home the point. +#### Enabling an authentication mechanism +An Authentication method is offered by the AS if its ACR value i.e. its corresponding custom script is `enabled`. -### B. Writing UI pages: -All pages are **xhtml** files. The Jans-auth server comes with a default set of pages for login, logout, errors, authorizations. You can easily override these pages or write new ones. You can easily apply your own stylesheet, images and resouce-bundles to your pages. +By default, users will get the default authentication mechanism as specified above. However, **using the OpenID Connect acr_values parameter, web and mobile clients can request any enabled authentication mechanism**. -This [article](../../customization/customize-web-pages) covers all the details you need to write your own web page. +1. Obtain the json contents of a custom script by using a jans-cli command like `get-config-scripts-by-type`, `get-config-scripts-by-inum` etc. + Example : + - `/opt/jans/jans-cli/config-cli.py --operation-id get-config-scripts-by-type --url-suffix type:PERSON_AUTHENTICATION` + - `/opt/jans/jans-cli/config-cli.py --operation-id get-config-scripts-by-inum --url-suffix inum:6122281b-b55d-4dd0-8115-b098eeeee2b7` -### C. Building business logic in Custom script: -Jans-auth server uses Weld 3.0 (JSR-365 aka CDI 2.0) for managed beans. The most important aspects of business logic are implemented through a set of beans. Details and examples of this can be found in this [article](../managed-beans.md) +2. [Update the custom script](https://github.com/JanssenProject/jans-cli/blob/main/docs/cli/cli-custom-scripts.md#update-an-existing-custom-script) and change the `enabled` attribute to `true` -### D. Adding libraries for use in the custom script -Java or Python libraries to be imported and used very easily. Remember incase you opt for a python library, it should be written in "pure python" only. -More details of this mentioned [here](../interception-scripts.md#using-python-libraries-in-a-script) +#### Level (rank) of an Authentication mechanism : +Each authentication mechanism (script) has a "Level" assigned to it which describes how secure and reliable it is. **The higher the "Level", higher is the reliability represented by the script.** Though several mechanisms can be enabled at the same Janssen server instance at the same time, for any specific user's session only one of them can be set as the current one (and will be returned as `acr` claim of id_token for them). If after initial session is created a new authorization request from a RP comes in specifying another authentication method, its "Level" will be compared to that of the method currently associated with this session. If requested method's "Level" is lower or equal to it, nothing is changed and the usual SSO behavior is observed. If it's higher (i.e. a more secure method is requested), it's not possible to serve such request using the existing session's context, and user must re-authenticate themselves to continue. If they succeed, a new session becomes associated with that requested mechanism instead. -## Uses of Person Authentication script +## Usage scenarios ### A. Implementing 2FA authentication mechanisms 1. [FIDO2](../../../script-catalog/person_authentication/fido2-external-authenticator/README) : Authentications using platform authenticators embedded into a person's device or physical USB, NFC or Bluetooth security keys that are inserted into a USB slot of a computer diff --git a/docs/admin/developer/scripts/update-token.md b/docs/admin/developer/scripts/update-token.md index 36fd0dd6bbf..306cb0bb991 100644 --- a/docs/admin/developer/scripts/update-token.md +++ b/docs/admin/developer/scripts/update-token.md @@ -1,18 +1,255 @@ ---- -tags: - - administration - - developer - - scripts ---- +## Overview +By overriding the interface methods in [`UpdateTokenType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/UpdateTokenType.java) inside a custom script you can -## This content is in progress +1. Enable transformation of claims and values in id_token and Access token e.g. add a custom claim to an `id_token`, change a token expiry, change the `sub` value, or remove the `nonce`. +
Example use-case: + * As per the open banking standard, the id_token should contain claim `openbanking_intent_id` and the same value should also reflect in the `sub` claim. + * As specified in the [FAPI Baseline Specification](https://openid.net/specs/openid-financial-api-part-1-1_0.html) the `sub` claim should have the user id. +2. Set a specific token lifetime +3. Perform extra business logic like adding or removing scopes. +4. Add an extra audit log for each token response. -The Janssen Project documentation is currently in development. Topic pages are being created in order of broadest relevance, and this page is coming in the near future. +## Flow +```mermaid -## Have questions in the meantime? +sequenceDiagram +title UpdateToken script +autonumber 1 +RP->>Jans AS: Request token +Jans AS->>Jans AS: Is UpdateToken script is associated with client?
(or is there a script that applies to all clients? ) +Jans AS->>Jans AS: do stuff +note right of Jans AS: do stuff
1. Enable transformation of claims and values in id_token
OR 2. set token expiry
or 3. add / remove scopes.
or 4. perform audit logs +Jans AS->>RP: return token(s) (Access token, ID token or Refresh Token) reflecting step 3 -While this documentation is in progress, you can ask questions through [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) or the [community chat on Gitter](https://gitter.im/JanssenProject/Lobby). Any questions you have will help determine what information our documentation should cover. +``` -## Want to contribute? +## Adding the custom script to Jans server -If you have content you'd like to contribute to this page in the meantime, you can get started with our [Contribution guide](https://docs.jans.io/head/CONTRIBUTING/). \ No newline at end of file +1. Create cs.json with the contents of a CUSTOM script. To do that, run the following command. +``` +/opt/jans/jans-cli/config-cli.py --schema /components/schemas/CustomScript > /tmp/cs.json +``` +2. Edit the file's contents to reflect the addition of the UpdateToken custom script. + + * Set enabled flag `true` + * Configure any parameters that the script may use. + * `name` field should reflect the use case + * `script_type` should be `UPDATE_TOKEN` + * `script.py` can have contents similar to [Sample Script](https://github.com/JanssenProject/jans/blob/main/docs/script-catalog/update_token/sample-script/sample_script.py)) and is present in jans-cli's host machine. + +``` + +{ + "dn": null, + "inum": null, + "name": "update_token", + "aliases": [], + "description": "Update token custom script", + "script": "_file /root/script.py", + "scriptType": "UPDATE_TOKEN", + "programmingLanguage": "JYTHON", + "moduleProperties": { + "value1": null, + "value2": null, + "description": null + }, + "configurationProperties": + +[ +{ + "value1": "param_name", + "value2": "DI3ICTTJKLL8PPPNGH7YI", + "description": "This is just an example", + "hide": true + }, + { + "value1": "param_name_2", + "value2": "eEbJdi3hg42zxyFYbHArU5RuioPP", + "description": "yet another example", + "hide": true + } +] +, + "level": "integer", + "revision": 0, + "enabled": true, + "scriptError": { + "raisedAt": null, + "stackTrace": null + }, + "modified": false, + "internal": false +} +``` + - Add the custom script. Save the response, it will contain the inum of the newly added script. +``` +/opt/jans/jans-cli/config-cli.py --operation-id post-config-scripts --data /tmp/cs.json +``` +## Associate an Update Token script to a client (RP) [optional step] +📝 Note: If the Update token script is not associated with a client, then it will be applicable to all clients registered in the Jans Server. Which implies that all tokens obtained using the Jans server will reflect modifications as per the script. +
+To Associate an Update Token script to a client (RP), execute the command below with appropriate values for: + - inum of the client + - inum of the update_token script + + ``` + /opt/jans/jans-cli/config-cli.py --operation-id patch-oauth-openid-clients-by-inum --url-suffix inum:inum_of_client + --data '[ + { + "op": "add", + "path": "updateTokenScriptDns", + "value": ["inum={SCRIPT_ID},ou=scripts,o=jans"] + } + ]' +``` + + +## Writing an Update token script (Pseudo code for potential usecases) + +### 1. Mandatory methods: +``` +class UpdateToken(UpdateTokenType): + + def __init__(self, currentTimeMillis): + self.currentTimeMillis = currentTimeMillis + + def init(self, customScript, configurationAttributes): + return True + + def destroy(self, configurationAttributes): + return True + + def getApiVersion(self): + return 11 +```` +### 2. modifyIdToken () : Used to modify claims in an ID token + +Pseudocode and example : +``` + # Returns boolean, true - indicates that script applied changes + # jsonWebResponse - is JwtHeader, you can use any method to manipulate JWT + # context is reference of io.jans.oxauth.service.external.context.ExternalUpdateTokenContext + def modifyIdToken(self, jsonWebResponse, context): + + # header claims + jsonWebResponse.getHeader().setClaim("header_name", "header_value") + + #custom claims + jsonWebResponse.getClaims().setClaim("openbanking_intent_id", openbanking_intent_id_value) + + #regular claims + jsonWebResponse.getClaims().setClaim("sub", claimValue) + + return True + +``` +![Inspecting a modified ID token](https://github.com/JanssenProject/jans/blob/main/docs/assets/update-id-token.png) + +### 3. modifyAccessToken(): +#### a. Granularity of access control: +An UpdateTokenType script is great for adding scopes or removing scopes to/from the Access token. By doing so you can tailor build the granularity of access control according to business need. + +[`context.overwriteAccessTokenScopes`](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalUpdateTokenContext.java) is ready to use method of the `context` variable + +``` + def modifyAccessToken(self, accessToken, context): + context.overwriteAccessTokenScopes(accessToken, Sets.newHashSet("openid", "mynewscope")) +``` +#### b. Perform business check before returning AT + +Pseudo code and example - Issue Access token only if account balance is greater than 0 +``` + # Returns boolean, true - indicates that script applied changes + # accessToken - is JwtHeader, you can use any method to manipulate JWT + # context is reference of io.jans.oxauth.service.external.context.ExternalUpdateTokenContext + def modifyAccessToken(self, accessToken, context): + + #read from session + sessionIdService = CdiUtil.bean(SessionIdService) + sessionId = sessionIdService.getSessionByDn(context.getGrant().getSessionDn()) # fetch from persistence + + org_id = sessionId.getSessionAttributes().get("org_id") + balance = thirdPartyApi.checkBalance(org_id) + + if balance > 0 : + return True + else: + return False # forbid the creation of AT +``` +#### c. Modify claims in an access token: +``` + # Returns boolean, true - indicates that script applied changes. If false is returned token will not be created. + # accessToken is reference of io.jans.as.server.model.common.AccessToken (note authorization grant can be taken as context.getGrant()) + # context is reference of io.jans.oxauth.service.external.context.ExternalUpdateTokenContext + def modifyAccessToken(self, accessToken, context): + + # header claims + accessToken.getHeader().setClaim("header_name", "header_value") + + #custom claims + accessToken.getClaims().setClaim("claim_name", "claimValue") + + #regular claims + accessToken.getClaims().setClaim("sub", claimValue) + + return True + +``` + +### 5. Modify a specific token lifetime based on the context: +1. Refresh token lifetime: +``` + def getRefreshTokenLifetimeInSeconds(self, context): + return 24 * 60 * 60 # one day +``` +2. ID token lifetime: +``` + def getIdTokenLifetimeInSeconds(self, context): + return 10 * 60 * 60 # 10 hours +``` +3. Access token lifetime: +``` + def getAccessTokenLifetimeInSeconds(self, context): + return 10 * 60 * 60 # 10 hours +``` + +### 6. modifyRefreshToken() : +Used to modify claims in a Refresh Token +``` + # Returns boolean, true - indicates that script applied changes. If false is returned token will not be created. + # refreshToken is reference of io.jans.as.server.model.common.RefreshToken (note authorization grant can be taken as context.getGrant()) + # context is reference of io.jans.as.server.service.external.context.ExternalUpdateTokenContext (in https://github.com/JanssenProject/jans-auth-server project, ) + def modifyRefreshToken(self, refreshToken, context): + return True + +``` +## IntrospectionType script vs UpdateTokenType script + +| | [`IntrospectionType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/introspection/IntrospectionType.java)| [`UpdateTokenType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/UpdateTokenType.java) | +|---|---|---| +| Client configuration parameter |Introspection script is invoked only when **`accessTokenAsJwt`=`true`** | Update token script is invoked irrespective of whether `accessTokenAsJwt` is `true` or `false` | +| Core Purpose | Used to return access token meta information like current validity, approved scopes, and information about the context in which the token was issued when a Resource Server which queries the [Introspection endpoint](https://datatracker.ietf.org/doc/html/rfc7662) | used to enable transformation of claims and values in id_token and Access token, set a specific token lifetime, change granularity of access control (up-scoping, down-scoping), audit logging for each token response, forbid the creation of AT based on a criteria. | +| Functionality | 1. Can be used to modify claims of an Access token as JWT, however this it is recommended to use UpdateToken script instead. |1. Used to modify id_token, refresh token and access token | +| |2. Introspection script cannot change scope of AT | 2. UpdateToken can change scope of AT and modify AT object in persistence irrespective of the value of `accessTokenAsJwt` as `true` or `false` | +| Script Invocation sequence |2. **After** an Access token is generated | 2. **Before** the creation of AT, id_token and refresh_token | + +## Testing + +1. Use this: [Reference for testing](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/AuthorizationCodeFlowHttpTest.java) +2. Inspect the tokens. Use [jwt.io](https://jwt.io) to inspect the contents of a JWT. + +## FAQ + +1. How can I add a `dict` type object as a claim value? +``` +from io.jans.as.model.uti import JwtUtil +from org.json import JSONObject; + + def modifyIdToken(self, jsonWebResponse, context): + datas = {'country': 'ID', 'sponsor': '7022952467', 'role': 'BusinessOwner', 'salesplanaff': '220', 'acctsubtype': 'BusinessOwner', 'accttype': 'AmBCBusiness', 'abo': '7022953754', 'aboname': 'NEW', 'lclpartyid': '119700175', 'email': None, 'status': 'Active'} + string_rep = json.dumps(datas) + jsonObject = JwtUtil.fromJson(string_rep) + jsonWebResponse.getClaims().setClaim("test", jsonObject) + print "Update token script. Modify idToken: %s" % jsonWebResponse + return True +``` diff --git a/docs/script-catalog/update_token/sample-script/README.md b/docs/script-catalog/update_token/README.md similarity index 100% rename from docs/script-catalog/update_token/sample-script/README.md rename to docs/script-catalog/update_token/README.md diff --git a/docs/script-catalog/update_token/sample-script/sample_script.py b/docs/script-catalog/update_token/sample_script.py similarity index 100% rename from docs/script-catalog/update_token/sample-script/sample_script.py rename to docs/script-catalog/update_token/sample_script.py