BookStack icon indicating copy to clipboard operation
BookStack copied to clipboard

OIDC Group Sync

Open ssddanbrown opened this issue 4 years ago • 11 comments
trafficstars

This issue has been opened to get feedback in relation to supporting group sync for OIDC usage. We'd look to implement group sync in a similar manner to LDAP and SAML2 for consistency.

In regards to feedback, I primarily want to know:

  • What do groups look like when provided by your IDP in the OIDC ID Token?

Knowing the above helps us plan a stable and well supported implementation.

ssddanbrown avatar Oct 25 '21 13:10 ssddanbrown

Hello there,

One popular solution for self-hosted OIDC, which may come in pair with a self-hosted BookStack would be Keycloak, which will usually return a list of groups as part of the token. So something like:

[...]
"groups": [
  "administrators",
  "group-1",
  "group-2",
],
[...]

Hope it helps,

Best,

git-noise avatar Nov 01 '21 15:11 git-noise

Would groups be synced to roles or to its own new(?) "group" concept? I would vote for the latter. Basically the concept of groups that is separated from roles. It could then serve as a container for multiple roles and also be able to "own" books and bookshelves.

hellerbarde avatar Dec 08 '21 09:12 hellerbarde

@hellerbarde Roles. As per our LDAP/SAML implementations. I wouldn't want to add another layer of grouping into this system as we'd likely be adding complexity with little benefit.

ssddanbrown avatar Dec 08 '21 13:12 ssddanbrown

Ok. Its just that the permission system seems rather opaque to non-technical end users. So far i had to fix permissions on every new book one of my users created so the correct group of people had access in the correct way to it. And i figured if I could assign a book to a "group" which then applies a pre-defined set of permissions, it would fix that problem.

But i understand if you dont want to load that added data model complexity on top.

Also, this is quickly going down a topic that doesnt belong in this issue, so i shall excuse myself. My question has been answered :)

Cheers, Phil

hellerbarde avatar Dec 08 '21 14:12 hellerbarde

As you know by helping me before, I am using django-oauth-toolkit to extend django as an ID Provider.

When having Abstract User Model, as always recomended in django, I had to get the step of writting the file oauth_validator.py, providing extra claims.

I've tested it adding groups generated by wagtail (a very good cms for django).

# oauth_validator.py
from oauth2_provider.oauth2_validators import OAuth2Validator

class CustomOAuth2Validator(OAuth2Validator):
    def get_additional_claims(self, request):
        groups = [
            group for group in request.user.groups.all()
        ]  # get all groups of the user
        return {
            "email": request.user.email,
            "first_name": request.user.first_name,
            "last_name": request.user.last_name,
            "groups": groups,
        }

The resulting OIDC_DUMP_USER_DETAILS=true is:

{
  "aud": "W****************************************N7m51caT",
  "iat": "*****************",
  "at_hash": "**********************",
  "sub": "1",
  "email": "[email protected]",
  "first_name": "Jesús",
  "last_name": "López de Leyva",
  "groups": [
    "Moderators",
    "Editors"
  ],
  "iss": "https://******************.ngrok.io",
  "exp": 1639077321,
  "auth_time": 1639041314,
  "jti": "b8102c32-****-4051-*****-******************"
}

deleyva avatar Dec 09 '21 09:12 deleyva

With OKTA, Groups can be retrieved and added as a group claim in a token - https://developer.okta.com/docs/guides/customize-tokens-groups-claim/main/#request-an-id-token-that-contains-the-groups-claim

alanmcseveney avatar Jan 27 '22 15:01 alanmcseveney

I'd prefer to use Role Bindings instead of Groups: Roles define what someone is allowed to do. Groups are just grouping Users.

That allows for a rather natural way of defining authorization infos (and is the way that e.g. KeyCloak supports). Furthermore, in KeyCloak you define Roles and Users on Realm-Level. Roles can be defined on Client-Level as well.

So, for managing Authorization in Bookstack, I'd define the Bookstack-specific Roles (Admin, Editor, Viewer, etc.) in the Bookstack-specific KeyCloak Client and would then assign these Roles to my Users and Groups I whish to grant access to.

That would result in an Access Token similar to this one:

{
  "exp": 1659004398,
  "iat": 1659004098,
  "jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "iss": "https://keycloak.instance",
  "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "typ": "Bearer",
  "azp": "bookstack",
  "session_state": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "resource_access": {
    "bookstack": {    <=== this is the client id
      "roles": [
        "Editor"      <=== this is a (maybe client specific) role name
      ]
    }
  },
  "scope": "openid uid groups profile email",
  "sid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "uid": "someuid",
  "email_verified": true,
  "name": "Michael Schaefers",
  "preferred_username": "mschaefers",
  "given_name": "Michael",
  "family_name": "Schaefers",
  "email": "[email protected]"
}

mschaefers avatar Jul 28 '22 10:07 mschaefers

Thanks everyone for your input, Looks like we can safely assume a nice flat array of simple group name strings then based upon the above, but they could be within a nested object structure. Should be pretty straightforward to handle, can have an option like OIDC_GROUPS_ATTRIBUTE=resource_access.bookstack.roles using the above as an example.

I've assigned this to the next feature release.

ssddanbrown avatar Jul 28 '22 15:07 ssddanbrown

Some last ideas :)

  1. It might be necessary to define a group name mapping if matching group names can not be provided by OIDC provider. (bookstackname=oidcname)
  2. The user trying to login should be unauthorized when there is no matching role provided

mschaefers avatar Jul 28 '22 15:07 mschaefers

@mschaefers We already have a scheme for custom mapping BookStack roles to auth system groups (Via a text field in the BookStack role configuration). We'll just use the same system here.

In regards to 2, I'm not going to do anything specifically to enforce that but you'd have the option to not assign a default role so they'd have access to the system but be without any roles assigned.

ssddanbrown avatar Jul 28 '22 16:07 ssddanbrown

Functionality added in #3616, ready to be part of the next feature release.

ssddanbrown avatar Aug 02 '22 16:08 ssddanbrown

Now merged, will be part of the next feature release

ssddanbrown avatar Aug 25 '22 10:08 ssddanbrown