Skip to content

Contargo/redmine_oidc

Repository files navigation

redmine_oidc

Developed for and used with the open source identity and access management tool Keycloak.

If you are not familiar with OpenID Connect visit OpenID Connect explained as a primer.

Setup

Migrations

Run migrations either implicitly in the context of a Capistrano deployment or explicitly with

bundle exec rails redmine:plugins:migrate NAME=redmine_oidc

Environment setup

  • Default URL

    routes.default_url_options[:protocol] = 'https'
    routes.default_url_options[:host] = 'localhost:3000'
    
  • Configure Redis as session (and cache) storage

    config.cache_store = :redis_cache_store, {
      url: 'redis://127.0.0.1:6379/0',
      reconnect_attempts: 1,
      expires_in: 1.day,
      race_condition_ttl: 3.seconds
    }
    config.session_store :cache_store,
                         key: '_redmine_session',
                         path: config.relative_url_root || '/',
                         secure: Rails.env.production?
    

    Session timeout is no longer determined by redmine. Thus, setting the expire_after argument in config.session_store or the global expires_in in config.cache_store will not have any effect, regarding the login time. Instead, you should set token expiry in your Keycloak realm settings

    • SSO Session Idle

    • SSO Session Max

    Nevertheless you should set an expires_in or expire_after in your cache or session config, respectively. This prevents the cache from being bloated with obsolete sessions. The value can be set in correlation with the identity provider’s session timeout. But its not mandatory, since the Redis session will be recreated as long as the identity provider’s session is still valid.

Plugin configuration

issuer_url

A discovery URL to your realm

Example: https://keycloak.your-company.com/auth/realms/your-realm

client_id

The client ID in ID provider, that is responsible for authentication.

Example: redmine

client_secret

The client secret to allow access by your trusted agent, i.e. a browser.

Example: 3618459c-f1f6-11ea-af1a-c7ab620b9443

scope

The scopes which are requested from your ID provider.

Example: openid profile email

unique_id_claim

The claim which will be compared with the oidc_identifier in the users table to identify the corresponding Redmine user.

Example: sub

roles_claim

The claim which provides the set of access roles.

Example: roles

access_roles

Space separated list of roles which make an authenticated user an authorized Redmine user. The roles are compared with the access token claim realm_access.roles.

Example: ROLES/REDMINE/ADMIN ROLES/REDMINE/USER

admin_role

Dedicated admin role which is looked for in the access tokes claim realm_access.roles.

Example: ROLES/REDMINE/ADMIN

Mapping users

If you use this plugin on a vanilla Redmine any user who gains access to Redmine for the first time will be created in the user table. Its reference to the OIDC provider subject, i.e. the content of the unique ID claim, is stored in the field oidc_identifier. This is the only attribute which maps a Redmine user to the corresponding OIDC provider subject. As a consequence, if you enable this plugin on a Redmine instance with a filled user table, you have to take care of connecting Redmine and OIDC provider users by yourself, e.g. by running a respective migration. This migration might use

  • users.login

  • or a combination of users.firstname and users.lastname

  • or email_addresses.address

as the currently and hopefully unique and immutable identifier to match users. Nevertheless, any of them should finally be superseded by a valid uuid, which has to be stored in users.oidc_identifier.

Avatars

If you activate the usage of Gravatar user icons by checking

  • Administration

    • Settings

      • Display

        • Use Gravatar user icons

this plugin tries to retrieve the avatar URL from the ID token picture claim, before falling back to Gravatar as the provider. picture is the standard claim and thus not configurable.

Design decisions

This section explains some design decisions and principles.

Used Libraries

OpenID Connect Server & Client Library

The OpenID Connect login process of redmine_oidc makes use of the client library parts of openid_connect and benefits from its request, token validation and decoding capabilities.

Monkey Patching

In order to implement another authentication process such as OpenID Connect we need to circumvent Redmine’s default login workflow. Fortunately, Ruby modules allow monkey patching by design. Nevertheless, we decided to keep the patch code as lean as possible. So, instead of patching the Redmine AccountController by inheriting and extending the class, both the ApplicationController and AccountController are only patched where absolutely necessary to achieve the workflow we need. They redirect to a dedicated OidcController which takes care of the OpenID Connect process. The OidcController inherits ApplicationController since we don’t need any of the user/password or OpenID authentication implementations of AccountController.

Besides the controller logic we need to patch the avatar acquisition, since we want to fetch a user’s avatar from a configurable claim and only fall back to gravatar, if the claim url doesn’t exist. Some other Redmine extensions like redmine_people and redmine_contacts_helpdesk do also patch the AvatarsHelper. They include their modifications. We prepend ours and thus win the battle for preference. But only until another plugin will make use of prepend and exploit its alphabetical position.

Session

We decided to put the OpenID Connect session information into the Redmine/Rails session and not into the database persistence. Session data is volatile information where access speed is more important than durability. Besides that there is no schema necessary for session data. A key-value storage with with adequate memory is sufficient. As a consequence the default cookie session store won’t work due to its storage limit of 4k. The solution is another little piece of infrastructure, Redis, which can also be used as cache backend.

Migrations

Two additional fields will be added to the users table by migrations automatically.

oidc_identifier

reference to a dedicated unique identifier claim

avatar_url

A url to a user’s avatar expected in the picture claim

We specify oidc_identifier as a reference to a unique oidc identifier. This can be any universally identifier claim of your OpenID Connect provider. Any other default user attributes, like login, email or lastname are prone to change.

As companies with a centralized authentication system like Keycloak might also provide their own avatar resource, we added an avatar_url field to users. This is filled during the login process with a dedicated configurable claim that refers to the user’s image by a URL.

Both fields are added directly to the table users, since a 1:1 relation to a dedicated table does not provide any advantage.

About

Redmine Plugin for OpenId Connect Login Support

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •