Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: implement account linking plugin #8091

Merged
merged 7 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/casa/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Besides a comprehensive graphical [admin console](./administration/admin-console

Casa is a plugin-oriented, Java web application. Existing functionality can be extended and new functionality and APIs can be introduced through plugins. Currently, there are plugins available for the following:

- [Accounts linking](./plugins/accts-linking/index.md)
- [Consent management](./plugins/consent-management.md)
- [Custom branding](./plugins/custom-branding.md)
- [2FA settings](./plugins/2fa-settings.md)
Expand Down
87 changes: 87 additions & 0 deletions docs/casa/plugins/accts-linking/accts-linking-agama.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Accounts linking project configuration

## Overview

The accounts linking Agama project must be configured in order to integrate third-party identity providers. The configuration of this project is supplied in a JSON file whose structure is like:

```
{
"io.jans.casa.acctlinking.Launcher": {

"providerID_1": { ... },
"providerID_2": { ... },
...
}
}
```

Each property part of the JSON object `io.jans.casa.acctlinking.Launcher` holds the configuration of a different identity provider. Here's a how a typical configuration of a provider looks like:

```
{
"displayName": "Goooogle",
"flowQname": "io.jans.inbound.GenericProvider",
"mappingClassField": "io.jans.casa.acctlinking.Mappings.GOOGLE",
"oauthParams": {
"authzEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"userInfoEndpoint": "https://www.googleapis.com/oauth2/v3/userinfo",
"clientId": "202403151302",
"clientSecret": "m|a1l2d3i4t5a6S7h8a9k0i'r¿a",
"scopes": ["email", "profile"]
}
}

```

In this case, we are populating the configuration of an OAuth-based provider called "Goooogle".

The tables shown in [this](https://github.com/JanssenProject/jans/blob/main/docs/agama-catalog/jans/inboundID/README.md#supply-configurations) page list all possible properties to configure a provider. Particularly, two properties deserve the most detail:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link should be a https://docs.jans.io . @ossdhaval this has to be added to the mkdocs and then referenced here.


1. `flowQname`. Agama projects are made up of flows - think of small "web journeys". This property must contain the name of an existing flow capable of interfacing with the identity provider of interest. Often, there is no need to write such "interfacing" flow. The below are ready-to-use and cover most of real-world cases, specifically OpenId/OAuth providers that support the **authorization code grant** (see section 1.3 of [rfc6749](https://www.ietf.org/rfc/rfc6749)):

- `io.jans.inbound.GenericProvider`. It implements the authorization code flow where the user's browser is taken to the external site. When authentication completes, a `code` is received at a designated redirect (callback) URL. With such `code` an access token is obtained as well as user's profile data. This flow supports _dynamic client registration_

- `io.jans.inbound.Apple`. It implements the authorization code flow with some nuances required in order to integrate "Apple Sign In"


2. `mappingClassField`. This is key for performing the attribute mapping process and the user provisioning. The remainder of this document is dedicated to these two aspects

!!! Note
Recall `enabled` is a handy property that can be used to temporarily "deactive" a given identity provider.

## Configuring attribute mappings

An introduction to attribute mapping can be found [here](https://github.com/JanssenProject/jans/blob/main/docs/agama-catalog/jans/inboundID/README.md#attribute-mappings). Unless an elaborated processing of attributes is required, a basic knowledge of Java language suffices to write a useful mapping.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again the same thing as above

CC @ossdhaval


To write a mapping, you can use the samples provided as a guideline (see folder `lib/io/jans/casa/acctlinking` in the Agama accounts linking project). You can add your mapping in the same file or create a new Java class for this purpose. Then save your changes, re-package (zip) the project, re-deploy, and update (re-import) the configuration if necessary.

Specifically, for Casa accounts linking, the mapping **must** include an attribute named `ID`. While `ID` is not part of the Jans database, here it is used to supply what could be understood as the _identifier_ of the user at the external site. For instance, in a social site this may be the username or email. The example below shows how to set `ID` assuming the username was returned by the external site in an attribute named `userId`:

```
profile -> {
Map<String, Object> map = new HashMap<>();

map.put("ID", profile.get("userId"));
...
return map;
}
```

In the above example, `profile` is a `Map<String, Object>` that holds the attribute-value pairs the third-party identity provider released for this user. For the interested, `profile` contents are dumped to the server logs (check `jans-auth_script.log`) so it is easy to peak into the values. Check for a message in `debug` level starting with "Profile data".

Both the ID of identity provider and the ID of the user will end up stored in an auxiliary database attribute. This helps to identify if the incoming user is already known (has been onboarded previously).

When the attribute mapping is applied, the `uid` attribute is set as well. This is the username the incoming user will be assigned in the local Jans database. The `uid` is automatically generated based on `ID` unless the mapping already populates the `uid` directly.

The return value of the mapping is a `Map<String, Object>`. This caters for cases where resulting attributes hold booleans, dates, numbers, strings, etc. When the attribute has to hold multiple values, you can use an array or a Java `Collection` object, like a `List`.

## User provisioning

After attribute mapping occurs, the local database is checked to determine if the incoming user is known (based on the `ID` in the mapping and the ID of the provider in question). If no match is found, the user is onboarded: a new entry is created in the database using the information found in the resulting mapping. Otherwise, the exact behavior varies depending on the provider configuration as follows:

- If `skipProfileUpdate` is set to `false`, the existing database entry is left untouched, otherwise
- If `cumulativeUpdate` is set to `false`, the existing attributes in the entry which are part of the mapping are overwritten
- If `cumulativeUpdate` is set to `true`, the existing attribute values in the entry are preserved and new values are added if present in the mapping

The updates just referenced apply to the matching entry based on mapping and provider ID, however, when `emailLinkingSafe` is set to `true` and the mapping comes with a `mail` value equal to an existing e-mail in the database, the update is carried over the e-mail matching entry.
99 changes: 99 additions & 0 deletions docs/casa/plugins/accts-linking/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Accounts Linking Plugin

## Overview

This plugin allows users to "link" their local Jans account with existing accounts at third-party identity providers like OIDC OPs and social sites, e.g. Apple, Facebook, Google, etc.

Besides the usual onboarding of a plugin jar file in Casa, administrators must deploy a number of additional components. This will be regarded later. By now let's summary key points of the accounts linking experience:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, lets summarize the key points of the ....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just make issues to track the changes to docs so we can start testing?


- When a user tries to login to Casa, the usual username/password form is presented but also a list of links that can take him to external sites (third-party identity providers) where authentication takes place
- Once authenticated, user profile data is grabbed from the external site - this is all backchannel
- A process called _attribute mapping_ is performed on profile data. This is a transformation process that turns incoming profile data into a shape compatible with a regular Jans database user entry
- If the mapped profile data matches an existing user in the Jans database, the existing entry is updated with the incoming data, otherwise, a new entry is inserted - this is called _user provisioning_. When provisioning occurs, the account has no password associated
- Finally the user is given access to Casa

From the perspective of a user already logged into Casa, the experience is as follows:

- In casa, a menu item is provided which takes the user to a (Casa) page that shows a list of third-party identity providers. For every provider there are options to trigger linking in case there is no account linked yet (external site authentication is launched), or to remove the linked account from the user profile
- If an account has no password assigned, removal of linked accounts is not allowed. However, a functionality for the user to assign himself a password is provided

## Components deployment

The pieces that allow materialization of the experience summarized above are the following:

a) The Casa plugin jar file

b) A custom XHTML page and jython script

c) The Agama inbound identity project

d) The Casa accounts linking Agama project

Most of work is demanded on setting up project _d_, where configuration of identity providers and attribute mapping tuning takes place.

In the following, it is assumed you have a VM-based installation of Jans Server (or Gluu Flex) available with Casa installed. In a separate machine, ensure you have SSH/SCP/SFTP access to such server and `git` installed.

!!! Note
For the below instructions ensure to replace `<jans-version>` with the version of your Jans Server

1. Download the plugin jar file `https://maven.jans.io/maven/io/jans/casa/plugins/acct-linking/<jans-version>/acct-linking-<jans-version>-jar-with-dependencies.jar` and copy to your server's `/opt/jans/jetty/jans-casa/plugins`

1. Download the utility jar file `https://maven.jans.io/maven/io/jans/agama-inbound/<jans-version>/agama-inbound-<jans-version>.jar` and copy to your server's `/opt/jans/jetty/jans-auth/custom/libs`

1. In the server, create a `casa` directory inside `/opt/jans/jetty/jans-auth/custom/pages`

1. Download the file `https://github.com/JanssenProject/jans/raw/main/jans-casa/plugins/acct-linking/extras/login.xhtml` and copy it to the previously created folder

1. Download the file `https://github.com/JanssenProject/jans/raw/main/jans-casa/plugins/acct-linking/extras/Casa.py`. Open TUI or the admin UI (for Flex), and locate the custom script whose name is `casa`. Update the contents of the script with the contents of the file

1. In TUI, ensure the custom script named `agama` is enabled

1. Still in TUI, visit the Clients screen, locate the client labeled "Client for Casa". Add the following redirect URI to the list: `https://<your-jans-host>/jans-casa/pl/acct-linking/user/interlude.zul`. Replace the name of your Jans server accordingly

1. Run the following commands to generate the archives of the Agama projects

```
git clone --depth 1 --branch main --no-checkout https://github.com/JanssenProject/jans.git
cd jans
git sparse-checkout init --cone
git sparse-checkout set docs/agama-catalog/jans/inboundID/project
git sparse-checkout set jans-casa/plugins/acct-linking/extras/agama
git checkout main
cd docs/agama-catalog/jans/inboundID/project
zip -r inbound.zip *
cd jans-casa/plugins/acct-linking/extras/agama
zip -r acctlinking.zip *
```

1. Transfer the zip files to a location in the server, deploy both archives using TUI (Agama menu)

1. Finally restart the authentication server

## Configuration

So far all components required for the Casa inbound identity solution are loaded in the server. When logging to casa, the form presented looks like usual, and once in, the "Accounts linking" menu takes to a page which hints about missing configuration.

The first step is figuring out the external sites to support. Keep in mind only OpenID or OAuth 2.0 based providers can be onboarded. There is not support for SAML IDPs.

The procedures for getting configuration settings in order to integrate third party providers vary widely. Here, only basic guidelines are given:

- If the provider is OpenId-compliant and supports dynamic client registration, obtain the OP URL and the scopes list to use when requesting user profile information. Most of times the scopes `openid`, `profile` and `email` will fit the bill

- If the provider is OpenId-compliant and does not support dynamic client registration, obtain the OP URL and scopes as in the previous case, and also a client ID and secret

- If the provider does not support OpenId. Obtain the following:

- The authorization endpoint URL
- The token endpoint URL
- The endpoint URL where profile data can be retrieved
- Client credentials (ID and secret)
- Scopes to use when requesting user profile information

The steps required to grab the above data vary among providers. Normally this is obtained through a sort of administrative developer GUI tool. If you are prompted for a "redirect URI" in such tool, provide `https://<your-jans-host>/jans-auth/fl/callback`.

Now it's time to supply the settings grabbed. The component these configurations are injected to is the Casa accounts linking Agama project. To make the effort easier, this project is bundled with some dummy configuration properties you can use as a template. Proceed as follows:

1. In TUI, open the Agama tab and scroll through the list of projects until the `casa-account-linking` is highlighted
1. Open the configuration management dialog (press `c`) and choose to export the sample configuration to a file on disk
1. Apply changes as needed - this is covered in a separate doc page [here](./accts-linking-agama.md). Note you can add or remove sections in the file at will, and that providers can also be disabled so they are not listed in the login page or in Casa app
1. Still in TUI, choose to import the file you have edited. Then conduct your testing
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public boolean isPassResetAvailable() {

public void reloadStatus() {

passSetAvailable = false; //Setting user's password is a disabled feature in Jans Casa
IdentityPerson p = persistenceService.get(IdentityPerson.class, persistenceService.getPersonDn(asco.getUser().getId()));
passSetAvailable = !p.hasPassword();
passResetAvailable = p.hasPassword() && confSettings.isEnablePassReset();

}
Expand Down
Loading