From dc8cb60990052256b46842f85ebf4961beee82dd Mon Sep 17 00:00:00 2001 From: Madhumita Subramaniam Date: Wed, 17 Aug 2022 19:54:49 +0530 Subject: [PATCH] fix: #2157 (#2159) --- docs/admin/developer/interception-scripts.md | 165 +++++++++++-------- docs/admin/developer/managed-beans.md | 138 ++++++++++++++++ 2 files changed, 233 insertions(+), 70 deletions(-) create mode 100644 docs/admin/developer/managed-beans.md diff --git a/docs/admin/developer/interception-scripts.md b/docs/admin/developer/interception-scripts.md index 0a1e784c647..8bececfbfe6 100644 --- a/docs/admin/developer/interception-scripts.md +++ b/docs/admin/developer/interception-scripts.md @@ -1,15 +1,15 @@ -# Overview +# Interception Scripts (or custom scripts) -Interception scripts (or custom scripts) allow you to define custom business logic for various features offered by the OpenID Provider (Jans-auth server). Some examples are - implementing a 2FA authentication method, consent gathering, client registration, adding business specific claims to ID token or Access token etc. +Interception scripts (or custom scripts) allow you to define custom business logic for various features offered by the OpenID Provider (Jans-auth server). Some examples of features which can be customized are - implementing a 2FA authentication method, consent gathering, client registration, adding business specific claims to ID token or Access token etc. Scripts can easily be upgraded and doesn't require forking the Jans Server code or re-building it. # Types of Interception scripts in Jans server Listed below, are custom scripts classified into various types, each of which represents a feature of the Jans server that can be extended as per the business need. Each script type is described by a java interface whose methods should be overridden to implement your business case. -1. [Person Authentication]() : Allows the definition of multi-step authentication workflows, including adaptive authentication - where the number of steps varies depending on the context. -1. Consent Gathering : Allows exact customization of the authorization (or consent) process. By default, the OP will request authorization for each scope, and display the respective scope description. -1. User Registration +1. [Person Authentication](https://jans.io/docs/admin/developer/scripts/person-authentication/) : Allows the definition of multi-step authentication workflows, including adaptive authentication - where the number of steps varies depending on the context. +1. [Consent Gathering](https://jans.io/docs/admin/developer/scripts/consent-gathering/) : Allows exact customization of the authorization (or consent) process. By default, the OP will request authorization for each scope, and display the respective scope description. +1. [User Registration]() 1. Update User -1. Client Registration +1. [Client Registration](https://jans.io/docs/admin/developer/scripts/client-registration/) 1. Dynamic scopes : Enables admin to generate scopes on the fly, for example by calling external APIs 1. ID Generator 1. Cache Refresh @@ -20,50 +20,24 @@ Listed below, are custom scripts classified into various types, each of which re 1. UMA 2 RPT Authorization Policies 1. UMA 2 Claims-Gathering -# An example -The example below is only meant to convey the concept, we will cover the details in later parts of the documentation. -Suppose, we are implementing an Openbanking Identity platform and we have to add business specific claims say `openbanking_intent_id` to the ID token. The custom script which will help us accomplish our goal is of the type `UpdateTokenType` where the `modifyIdToken` method has to be implemented. A sample custom script with this business logic will be as stated below : -``` -class UpdateToken(UpdateTokenType): - def __init__(self, currentTimeMillis): - self.currentTimeMillis = currentTimeMillis - - def init(self, customScript, configurationAttributes): - < initialization code comes here > - return True - - def destroy(self, configurationAttributes): - < clean up code comes here> - return True - - def getApiVersion(self): - return - def modifyIdToken(self, jsonWebResponse, context): - - # Step1: - sessionId = context.getSession() - openbanking_intent_id = sessionId.getSessionAttributes().get("openbanking_intent_id ") - - # Step2: - jsonWebResponse.getClaims().setClaim("openbanking_intent_id ", openbanking_intent_id ) - -``` # Implementation languages - Jython or pure Java -Interception scripts are written in [Jython](http://www.jython.org/) or in Java, enabling Java or Python libraries to be imported. While the syntax of the script requires Python for Jython, most of the functionality can be written in Java. +Interception scripts are written in **[Jython](http://www.jython.org/)** or in **pure Java**, enabling Java or Python libraries to be imported. + +*** -## Using pure Java scripts +## Implementation in Pure Java -Under script in this case means java source file (e.g. `Discovery.java`) which is compiled by AS and executed at runtime. +A script in Java refers to a java source file (e.g. `Discovery.java`) which is compiled by AS and executed at runtime. -Notes: -* there not package set in pure java scripts -* name of the class must match to the name set in [CustomScriptType source code](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java) (e.g. for discovery script it is "Discovery") -* scripts must implement predefined interface which can be found in [CustomScriptType source code](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java) -* all libraries available at runtime to server are available also to pure java script -* to log to `jans-auth_script.log` use `scriptLogger` -* normal log will log in to `jans-auth.log` +Some rules: +* The java class file containing the script should not have a package set. +* The name of the class must match to the name set in [CustomScriptType source code](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java) (e.g. for discovery script it is "Discovery") +* Scripts must implement predefined interface which can be found against the [CustomScriptType](https://github.com/JanssenProject/jans/tree/main/jans-core/script/src/main/java/io/jans/model/custom/script/type). For e.g. if you are writing a Person authentication script then your class should implement the [following interface](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/auth/PersonAuthenticationType.java) +* All libraries available at runtime to server are available also to pure java script +* To log to `jans-auth_script.log` use `scriptLogger` +* Normal log will log in to `jans-auth.log` **Example pure java script** ``` @@ -119,26 +93,49 @@ public class Discovery implements DiscoveryType { } ``` +### Using Java libraries in a script: +
**Steps:** +1. Add library jars to `/opt/jans/jetty/jans-auth/custom/libs/` +2. Edit /opt/jans/jetty/jans-auth/webapps/jans-auth.xml and add the following line replacing the word `library-name` with the actual name of the library: +``` +/opt/jans/jetty/jans-auth/custom/libs/library-name.jar +``` +3. Restart jans-auth service +`systemctl restart jans-auth` -## Using Python libraries in a script: -### Caution: -1. You can only use libraries (packages and modules) that are written in **Pure Python**. Importing a Python class which is a wrapper around a library written in C is not supported by the Jans server. As an example, the psycopg2 library used to connect to PostgreSQL from Python. Since it is a C wrapper around libpq, it won't work with Jython. +*** -1. Python 3 packages / modules are not supported. +## Implementation in Jython +The example below is only meant to convey the concept, we will cover the details in later parts of the documentation. +Suppose, we are implementing an Openbanking Identity platform and we have to add business specific claims say `openbanking_intent_id` to the ID token. The custom script which will help us accomplish our goal is of the type `UpdateTokenType` where the `modifyIdToken` method has to be implemented. A sample custom script with this business logic will be as stated below : +``` +class UpdateToken(UpdateTokenType): + def __init__(self, currentTimeMillis): + self.currentTimeMillis = currentTimeMillis -### Steps: -1. Pure Python libraries should be added to `/opt/jans/python/libs` + def init(self, customScript, configurationAttributes): + < initialization code comes here > + return True -2. Using pip to install additional Python packages: + def destroy(self, configurationAttributes): + < clean up code comes here> + return True -* Find out about your Jython version first. cd into the /opt directory in your Jans Server container and run ls. A directory named jython- should be listed too where will correspond to the Jython version. Note the version. -* Open the file `/etc/jans/conf/jans.properties` and look for the line starting with `pythonModulesDir=`. Append the value `/opt/jython-/Lib/site-packages` to any existing value. Each value is separater by a colon (:). It should look something like this ` pythonModulesDir=/opt/jans/python/libs:/opt/jython-2.7.2a/Lib/site-packages` -Run the following command ` /opt/jython-/bin/jython -m ensurepip ` -Install your library with `/opt/jython-/bin/pip install ` where is the name of the library to install. -* Restart the jans-auth service : `systemctl restart jans-auth` + def getApiVersion(self): + return -## Using Java libraries in a script: -### Steps: + def modifyIdToken(self, jsonWebResponse, context): + + # Step1: + sessionId = context.getSession() + openbanking_intent_id = sessionId.getSessionAttributes().get("openbanking_intent_id ") + + # Step2: + jsonWebResponse.getClaims().setClaim("openbanking_intent_id ", openbanking_intent_id ) + +``` +### Using Java libraries in a Jython script: +
**Steps:** 1. Add library jars to `/opt/jans/jetty/jans-auth/custom/libs/` 2. Edit /opt/jans/jetty/jans-auth/webapps/jans-auth.xml and add the following line replacing the word `library-name` with the actual name of the library: ``` @@ -147,19 +144,41 @@ Install your library with `/opt/jython-/bin/pip install 3. Restart jans-auth service `systemctl restart jans-auth` +### Using Python libraries in a script: + +1. You can only use libraries (packages and modules) that are written in **Pure Python**. Importing a Python class which is a wrapper around a library written in C is not supported by the Jans server. As an example, the psycopg2 library used to connect to PostgreSQL from Python. Since it is a C wrapper around libpq, it won't work with Jython. + +1. Python 3 packages / modules are not supported. + +
**Steps:** +1. Pure Python libraries should be added to `/opt/jans/python/libs` + +2. Using pip to install additional Python packages: + +* Find out about your Jython version first. cd into the /opt directory in your Jans Server container and run ls. A directory named jython- should be listed too where will correspond to the Jython version. Note the version. +* Open the file `/etc/jans/conf/jans.properties` and look for the line starting with `pythonModulesDir=`. Append the value `/opt/jython-/Lib/site-packages` to any existing value. Each value is separater by a colon (:). It should look something like this ` pythonModulesDir=/opt/jans/python/libs:/opt/jython-2.7.2a/Lib/site-packages` +Run the following command ` /opt/jython-/bin/jython -m ensurepip ` +Install your library with `/opt/jython-/bin/pip install ` where is the name of the library to install. +* Restart the jans-auth service : `systemctl restart jans-auth` +### Debugging a Jython script +This [article](https://github.com/JanssenProject/jans/blob/main/docs/admin/developer/interception-scripts-debug.md) covers the details. +*** -# Mandatory methods in any Custom script +### Mandatory methods to be overridden +This is the [base class of all custom script types](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/BaseExternalType.java) and all custom scripts should implement the following methods. * `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 * `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 * `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 -# Configurable properties of a custom script +*** + +### Configurable properties of a custom script - + @@ -173,7 +192,13 @@ Install your library with `/opt/jython-/bin/pip install
Name unique identifier for the custom script e.g. update_user
Name unique identifier(name) for the custom script e.g. person_authentication_google
Description Description text
Programming Languages Python
Level Used in Person Authentication script type, the strength of the credential is a numerical value assigned to the custom script that is tied to the authentication method. The higher the value, the stronger it is considered. Thus, if a user has several credentials enrolled, he will be asked to present the one of them having the highest strength associated.
Custom propertiesKey - value pairs for configurable parameters like Third Party API keys, location of configuration files etc
-# Operations on custom scripts using jans-cli +*** +### Building business logic in a 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. This [article](https://jans.io/docs/admin/developer/managed-beans.md) presents many ready-to-use beans which can be used to build a script. + +*** + +### Operations on custom scripts using jans-cli Jans-cli supports the following six operations on custom scripts: @@ -186,7 +211,7 @@ Jans-cli supports the following six operations on custom scripts: 6. `delete-config-scripts-by-inum`, requires an argument `--url-suffix inum: _____` The post-config-scripts and put-config-scripts require various details about the scripts. The following command gives the basic schema of the custom scripts to pass to these operations. -## Basic schema of a custom script +### Basic schema of a custom script Command: `/opt/jans/jans-cli/config-cli.py --schema /components/schemas/CustomScript ` @@ -251,7 +276,10 @@ To add or modify a script first, we need to create the script's python file (e.g "internal": false } ``` -## Add, Modify and Delete a script + +*** + +### Add, Modify and Delete a script The following command will add a new script with details given in /tmp/sample.json file. __The jans-cli will generate a unique inum of this new script if we skip inum in the json file.__ ``` @@ -294,10 +322,7 @@ E.g: ``` -# Client specific implementations - +## Client specific implementations -### -# FAQs and troubleshooting -# Useful links -1. [Custom scripts and jans-cli](https://github.com/JanssenProject/jans-cli/blob/main/docs/cli/cli-custom-scripts.md#find-list-of-custom-scripts) +## Useful links +1. [Custom scripts and jans-cli](https://github.com/JanssenProject/jans-cli/blob/main/docs/cli/cli-custom-scripts.md#find-list-of-custom-scripts) \ No newline at end of file diff --git a/docs/admin/developer/managed-beans.md b/docs/admin/developer/managed-beans.md new file mode 100644 index 00000000000..2bec7b464a3 --- /dev/null +++ b/docs/admin/developer/managed-beans.md @@ -0,0 +1,138 @@ +## Ready-to-use code 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 some of which are listed below: + +### 1. [AuthenticationService](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/AuthenticationService.java) + +Allows to authenticate a user or obtain the current authenticated user +
+Relevant methods: + +|Signature|Description| +|-|-| +|`boolean authenticate(String userName)`|Performs authentication for the user whose identifier (`userName`) is passed as parameter| +|`boolean authenticate(String userName, String password)`|Performs authentication for the user whose identifier (`userName`) is passed as parameter. The `password` supplied must be the correct password of the user in question| +|`User getAuthenticatedUser()`|Returns a representation of the currently authenticated user. `null` if no user is currently authenticated. See [User](#class-user) data object| + +### 2. [Authenticator](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/auth/Authenticator.java) +This class is mainly used in facelets templates for authentication flows to proceed in the sequence of steps. +Relevant methods: + +|Signature|Description| +|-|-| +|boolean authenticate()|Makes the authentication flow proceed by calling the `authenticate` method of the custom script| +|String prepareAuthenticationForStep()|Makes the authentication flow proceed by calling the `prepareForStep` method of the custom script| + +### 3. [UserService](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/UserService.java) +Allows CRUD of users in the local persistence. + +Relevant methods: + +|Signature|Description| +|-|-| +|`User addUser(User user, boolean active)`|Creates a new user based on the representation passed as parameter. `active` parameter denotes whether user status (`gluuStatus` attribute) will be `active` or `register`| +|`User addUserAttribute(String userId, String attributeName, String attributeValue)`|Adds an attribute to the user identified by `userId` in the database with the name and value passed. Returns a representation of the modified user or `null` in case of failure or if such name/attribute is already part of such user| +|`boolean addUserAttribute(User user, String attributeName, String attributeValue)`|Adds an attribute to the `user` object with the name and value passed. This method only alters the `user` argument and does not persist changes. Returns `false` if such name/attribute is already part of `user` +|`User addUserAttributeByUserInum(String userInum, String attributeName, String attributeValue)`|Adds an attribute to the user whose `inum` attribute (in the database) equals to `userInum` using the name and value passed. Returns a representation of the modified user or `null` in case of failure or if such name/attribute is already part of such user| +|`CustomAttribute getCustomAttribute(User user, String attributeName)`|Gets a representation of the attribute whose name is passed for the user in question (`user`). Returns `null` if no such attribute is populated| +|`String getDnForUser(String inum)`|Obtains the DN (distinguished name) of the user whose `inum` attribute equals to `userInum` (no check that such user may exist is actually made)| +|`User getUser(String userId, String... returnAttributes)`|Retrieves a user representation for the user identified with `userId` containing only the attributes requested (`returnAttributes`). `null` is returned if no such user exists| +|`User getUserByAttribute(String attributeName, String attributeValue)`|Retrieves a user (first available) such that the attribute referenced (`attributeName`) has the value passed (`attributeValue`). `null` is returned if no such user exists| +|`String getUserInum(String userId)`|Retrieves the `inum` database attribute for the user identified with `userId`.`null` is returned if no such user exists| +|`User removeUserAttribute(String userId, String attributeName, String attributeValue)`|Removes `attributeValue` from the values of the attribute whose name is passed (`attributeName`) for the user identified with `userId`| +|`User replaceUserAttribute(String userId, String attributeName, String oldAttributeValue, String newAttributeValue)`|Updates the user identified with `userId` by replacing the value of the attribute `attributeName` with the value passed. `null` is returned if no such user exists| +|`void setCustomAttribute(User user, String attributeName, String attributeValue)`|Sets the value of the attribute `attributeName` with the single value `attributeValue` for the user representation passes as parameter. This method does not persist changes| +|`User updateUser(User user)`|Updates the user represented by `user` object in the database| + +### 4. [User](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/common/src/main/java/io/jans/as/common/model/common/User.java) +A class employed to represent a user entry in the persistence. Provides getters and setters to retrieve and assign value(s) for attributes + +### 5. [CustomAttribute](https://github.com/JanssenProject/jans/blob/main/jans-orm/model/src/main/java/io/jans/orm/model/base/CustomAttribute.java) +A class that models an attribute. An attribute has a name and a collection of associated values + +### 6. [Identity](https://github.com/JanssenProject/jans/blob/main/jans-core/service/src/main/java/io/jans/model/security/Identity.java) +Mainly used to carry data between steps of authentication flows. + +|Signature|Description| +|-|-| +|`Object getWorkingParameter(String name)`|Retrieves a working parameter by name previously set via `setWorkingParameter`| +|`void setWorkingParameter(String name, Object value)`|Binds data to a name for further use in an authentication flow. Recommended values to store are `String`s| +|`SessionId getSessionId()`|Retrieves a reference to the associated server session object, see [SessionId](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/SessionId.java)| + +### 7. HttpService: [HttpService](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/net/HttpService.java) + +Provides utility methods to execute HTTP requests, manipulate responses, etc + +Relevant methods: + +|Signature|Description| +|-|-| +|`HttpClient getHttpsClient()`|Returns an instance of `org.apache.http.client.HttpClient` (see oxcore-util class [SslDefaultHttpClient](https://github.com/JanssenProject/jans/blob/main/jans-core/util/src/main/java/io/jans/net/SslDefaultHttpClient.java))| +|`HttpServiceResponse executeGet(HttpClient httpClient, String requestUri)`|Perform a GET on the URI requested. Returns an instance of [io.jans.as.server.model.net.HttpServiceResponse](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/model/net/HttpServiceResponse.java) (a wrapper on `org.apache.http.HttpResponse`)| +|`byte[] getResponseContent(HttpResponse httpResponse)`|Consumes the bytes of the associated response. Returns `null` if the response status code is not 200 (OK)| + +### 8. [CacheService](https://github.com/JanssenProject/jans/blob/main/jans-core/cache/src/main/java/io/jans/service/CacheService.java) +Provides a unified means to interact with the underlying cache provider configured in the Jans-auth Server + +Relevant methods: + +|Signature|Description| +|-|-| +|`void clear()`|Flushes the whole cache| +|`Object get(String key)`|Retrieves the value of `key` in the cache. `null` if there is no such key present| +|`void put(int expirationInSeconds, String key, Object object)`|Puts an object in the cache associated to the key passed. An expiration in seconds can be provided| +|`put(String key, Object object)`|Puts an object in the cache associated to the key passed. The expiration used is the default expiration configured in Gluu| +|`void remove(String key)`|Removes an entry from the cache| + +### 9. [FacesService](https://github.com/JanssenProject/jans/blob/main/jans-core/jsf-util/src/main/java/io/jans/jsf2/service/FacesService.java) : Provides utilities to properly build encoded URLs and make redirections. This class is used a great deal in custom scripts + +Relevant methods: + +|Signature|Description| +|-|-| +|`void redirectToExternalURL(String url)`|Redirects the user's browser to the URL passed as parameter| +|`String encodeParameters(String url, Map parameters)`|Builds a URL by appending query parameters as supplied in `parameters` map. Every value in the map is properly URL-encoded| + +### 10. [FacesMessages](https://github.com/JanssenProject/jans/blob/main/jans-core/jsf-util/src/main/java/io/jans/jsf2/message/FacesMessages.java) +Allows manipulation of JSF context messages + +Relevant methods: + +|Signature|Description| +|-|-| +|`void add(Severity severity, String message)`|Adds a message to the JSF context with the severity (`javax.faces.application.FacesMessage.Severity`) specified| +|`void clear()`|Clears the messages of the JSF context| +|`String evalAsString(String expression)`|Evaluates an EL expression using the JSF context and returns the result as a String| +|`void setKeepMessages()`|Sets the "keep messages" property of the JSF flash| + + +### 11. [CdiUtil](https://github.com/JanssenProject/jans/blob/main/jans-core/service/src/main/java/io/jans/service/cdi/util/CdiUtil.java) : Allows to obtain references of managed beans. This is particularly useful in custom scripts + +Relevant methods: + +|Signature|Description| +|-|-| +| T bean(Class clazz)|Gets the managed bean belonging to the class passed as parameter| + +Example (jython code): + +``` +from org.gluu.oxauth.service import UserService +from org.gluu.oxauth.service import AuthenticationService +... +userService = CdiUtil.bean(UserService) +authenticationService = CdiUtil.bean(AuthenticationService) +``` + +### 12. [StringHelper](https://github.com/JanssenProject/jans/blob/main/jans-core/util/src/main/java/io/jans/util/StringHelper.java) + Provides many utility methods that often arise in the manipulation of Strings + +### 13. [EncryptionService](https://github.com/JanssenProject/jans/blob/main/jans-scim/service/src/main/java/io/jans/scim/service/EncryptionService.java) + Allows to encrypt/decrypt strings using a 3DES cipher whose salt is found in `/etc/jans/conf/salt` + +Relevant methods: + +|Signature|Description| +|-|-| +|String decrypt(String encryptedString)|Decrypts the encrypted string supplied| +|Properties decryptAllProperties(Properties connectionProperties)|Returns a `java.util.Properties` object with all decrypted values found in `connectionProperties`| +|`String encrypt(String unencryptedString)`|Encrypts the string supplied| \ No newline at end of file