paperless-ngx icon indicating copy to clipboard operation
paperless-ngx copied to clipboard

Feature: Add OpenID Connect SSO support via django-allauth

Open smkent opened this issue 2 years ago • 31 comments

Proposed change

I use OpenID Connect (OIDC) for SSO with my self-hosted web apps. I've been excited to try out paperless-ngx for a while, but there is currently no support for OpenID Connect. This change adds support for OpenID Connect for SSO via django-allauth, with the potential for also supporting django-allauth's numerous SSO options.

OpenID Connect support enables SSO use via self-hosted providers like Authentik and Keycloak.

~Note: This PR is based on my upstream PR in django-allauth. This code works with a small Dockerfile change (see below) but should be considered a working concept until my upstream PR is merged.~ django-allauth 0.52.0 has been released which contains support for OpenID Connect!

Sample OIDC configuration:

PAPERLESS_SSO_OIDC_NAME="Test OIDC"
PAPERLESS_SSO_OIDC_URL="https://some.oidc.server.example.com"
PAPERLESS_SSO_OIDC_CLIENT_ID="some-client-id-from-your-oidc-server"
PAPERLESS_SSO_OIDC_SECRET="accompanying-secret-from-your-oidc-server"

With no SSO configured, this PR does not change the login behavior:

paperless-password

However, SSO options will now appear on the login screen when configured:

paperless-both

If SSO is configured, it is also possible to hide the password form via a new setting PAPERLESS_LOGIN_HIDE_PASSWORD_FORM:

paperless-sso

Type of change

  • [ ] Bug fix (non-breaking change which fixes an issue)
  • [x] New feature (non-breaking change which adds functionality)
  • [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • [ ] Other (please explain)

Checklist:

  • [x] I have read & agree with the contributing guidelines.
  • [x] If applicable, I have tested my code for new features & regressions on both ✅ mobile & ✅ desktop devices, using the latest version of major browsers.
  • [ ] If applicable, I have checked that all tests pass, see documentation.
  • [x] I have run all pre-commit hooks, see documentation.
  • [ ] I have made corresponding changes to the documentation as needed.
  • [x] I have checked my modifications for any breaking changes.

smkent avatar Oct 05 '22 05:10 smkent

Hello @smkent,

thank you very much for submitting this PR to us!

This is what will happen next:

  1. My robotic colleagues will check your changes to see if they break anything. You can see the progress below.
  2. Once that is finished, human contributors from paperless-ngx review your changes. Since this is a non-trivial change, a review from at least two contributors is required.
  3. Please improve anything that comes up during the review until your pull request gets approved.
  4. Your pull request will be merged into the dev branch. Changes there will be tested further.
  5. Eventually, changes from you and other contributors will be merged into main and a new release will be made.

Please allow up to 7 days for an initial review. We're all very excited about new pull requests but we only do this as a hobby. If any action will be required by you, please reply within a month.

Pull Request Test Coverage Report for Build 3909301335

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 60 unchanged lines in 1 file lost coverage.
  • Overall coverage decreased (-0.9%) to 91.757%

Files with Coverage Reduction New Missed Lines %
paperless/settings.py 60 77.78%
<!-- Total: 60
Totals Coverage Status
Change from base Build 3896206563: -0.9%
Covered Lines: 5443
Relevant Lines: 5932

💛 - Coveralls

coveralls avatar Oct 05 '22 13:10 coveralls

This is awesome, thanks for the PR. Let's keep an eye on your upstream PR and can circle back to this hopefully soon. When the time comes I think we should update the documentation to reflect this too. Bravo 👏

shamoon avatar Oct 05 '22 15:10 shamoon

Looking forward for this PR being approved!

olant avatar Nov 03 '22 08:11 olant

Not sure if this will be useful at all but I did something similar a while ago and did a write up about it. https://ciphermenial.github.io/posts/adding-oauth-to-paperless/

ciphermenial avatar Dec 07 '22 23:12 ciphermenial

please merge! Using a central auth is an essential feature in my opinion

MartinVerges avatar Dec 09 '22 08:12 MartinVerges

This PR is a draft for the reasons stated when it opened. Once django-allauth has merged and released, it will get reviewed and merged.

stumpylog avatar Dec 09 '22 15:12 stumpylog

I'm waiting the merge because it's a really useful feature for my usecase ! Thank you for your work, it's amazing !

rmylb avatar Dec 14 '22 12:12 rmylb

well well https://github.com/pennersr/django-allauth/pull/3165 !

shamoon avatar Dec 15 '22 19:12 shamoon

Alas just waiting on the maintainer of that package to push a new release first, but yes, good news regardless!

Celant avatar Dec 15 '22 19:12 Celant

Version 0.52.0 of django-allauth has just been released. Can't wait to test this with authelia.

hendrik1120 avatar Dec 29 '22 19:12 hendrik1120

I've tested Django allauth over on Tandoor recipes, works pretty well Authelia!

ikaruswill avatar Jan 07 '23 03:01 ikaruswill

@smkent Any free time to resolve the conflicts and mark this as ready? I think people would love to see it in

stumpylog avatar Jan 11 '23 01:01 stumpylog

I am wondering about the order of things with this vs #2147 , which I know is a beast but would like to get to soon. My thinking was it would be better to merge that in before this, but not like that’s a hard requirement or anything

shamoon avatar Jan 11 '23 01:01 shamoon

I've updated this PR with django-allauth 0.52.0 and merged in dev to resolve the merge conflicts.

I'm interested in writing unit tests for this feature. Please let me know what else I can do to help!

smkent avatar Jan 13 '23 07:01 smkent

I have no idea how to write testing for the feature, but perhaps the allauth tests can be looked to for an example. It would be best to have the feature covered.

stumpylog avatar Jan 18 '23 01:01 stumpylog

Recently Gitea had implemented some oauth2 tests which can give some idea on the proper flow of tests and which endpoints to call. While oidc is not entirely oauth2, I think it can at least give some general hints about what to test.

SpiderD555 avatar Jan 18 '23 12:01 SpiderD555

I'd like to start testing the image out and see how it works, but one thing still lacking from the PR is documentation of the feature. There's a number of new settings to document and I think one or two setup guides would be very helpful. These could be generic, or could be a little more specific to some of the common tools like Authelia and Authentik

stumpylog avatar Jan 25 '23 17:01 stumpylog

Hi, I already did some basic testing with authelia last week and the setup was very easy except one issue I encountered. Just adding a new OIDC client in authelia and setting 4 envs went pretty smoothly, but authelia also requires a redirect_uri to be set on both sides, which must be exactly the same.

Authentication always failed because django-allauth set the redirect_uri to https:// which I wasn't even using. I couldn't find anything in the documentation to change this, and also manually setting the PAPERLESS_URL to http:// didn't help. So I set up a local reverse proxy to not bother with django ssl certs and finally got this working.

Hope this helps if someone is encountering the same issue, and pls let me know if there is a way to change this config.

hendrik1120 avatar Jan 25 '23 18:01 hendrik1120

Is there a way to test this/does a Docker image already exist? I just switched all my services to Keycloak and can't wait to try out paperless-ngx! :)

mwllgr avatar Jan 30 '23 17:01 mwllgr

redirect_uri

Hi, I already did some basic testing with authelia last week and the setup was very easy except one issue I encountered. Just adding a new OIDC client in authelia and setting 4 envs went pretty smoothly, but authelia also requires a redirect_uri to be set on both sides, which must be exactly the same.

Authentication always failed because django-allauth set the redirect_uri to https:// which I wasn't even using. I couldn't find anything in the documentation to change this, and also manually setting the PAPERLESS_URL to http:// didn't help. So I set up a local reverse proxy to not bother with django ssl certs and finally got this working.

Hope this helps if someone is encountering the same issue, and pls let me know if there is a way to change this config.

how do you set the redirect_uri in Authelia?

lazyzyf avatar Feb 08 '23 15:02 lazyzyf

I've tested Django allauth over on Tandoor recipes, works pretty well Authelia!

how did you set the redirect_uris in Authelia?

lazyzyf avatar Feb 09 '23 02:02 lazyzyf

how do you set the redirect_uri in Authelia?

I would say that question is better suited for Authelia and not related to this issue here, but to be sure it is well documented here: https://www.authelia.com/configuration/identity-providers/open-id-connect/. You just set the redirect_uris in your config, but again this issue and this repo is not about Authelia, but about implementing OIDC support for paperless-ngx via django-allauth. This means any OIDC provider should work.

@hendrik1120 thanks for your testing, I think I'll want to use this setup as well now since I'm already using HTTPS for everything as well as Authelia.

DennisGaida avatar Feb 09 '23 08:02 DennisGaida

It works! Whoop Whoop! 🎉🪩🎉

Step-by-step repo for a docker container (@mwllgr asked). This is a very "hacky" approach but works ™ - don't use in production etc.

  1. create a directory for all source files mkdir oidc-patch

  2. get the current sources git checkout https://github.com/paperless-ngx/paperless-ngx

  3. fetch the files from the pull request git fetch origin pull/1746/head:oidc-patch

  4. edit the file oidc-patch/docker/docker-entrypoint.sh We need to install django-allauth on container startup since this wasn't done during container creation, so add in e.g line 82 pip install -v "django-allauth~=0.52" Note: this means startup will take a while because django-allauth is installed everytime, of course this would need to go into the Dockerfile, but hey - we are testing

  5. adjust your docker-compose You're pretty much just mapping the current /dev directory into the container as well as overwrite the entrypoint for allauth installation. You need to adjust the paths to where you cloned the PR files.

    volumes:
      [...]
     ### oidc-patch
      - $DOCKERDIR/paperless/oidc-patch/src:/usr/src/paperless/src
      - $DOCKERDIR/paperless/oidc-patch/docker/docker-entrypoint.sh:/sbin/docker-entrypoint.sh
  1. Set the environment variables
    environment:
      [...]
      ### oidc-patch
      PAPERLESS_SSO_OIDC_NAME: "Authelia"
      PAPERLESS_SSO_OIDC_URL: "<authelia-url>"
      PAPERLESS_SSO_OIDC_CLIENT_ID: "<client-id>"
      PAPERLESS_SSO_OIDC_SECRET: "<client-secret>"
      PAPERLESS_LOGIN_HIDE_PASSWORD_FORM: "false"
  1. In Authelia / your OIDC provider create a new config
      - id: <client-id>
        description: Paperless OIDC
        secret: <client-secret>
        scopes:
          - openid
          - profile
          - email
        redirect_uris:
          - https://<paperless-url>/accounts/authelia/login/callback/

Note: The callback URL is HTTPS and has a trailing slash and I believe the authelia part in the URL stems from PAPERLESS_SSO_OIDC_NAME, but I didn't dig into this.

DennisGaida avatar Feb 09 '23 09:02 DennisGaida

There's also the pre-built image ghcr.io/smkent/paperless-ngx:feature-oidc-allauth

@DennisGaida That's detailed setup, thanks. I may be able to document setup on this side enough with those.

stumpylog avatar Feb 09 '23 15:02 stumpylog

There's also the pre-built image ghcr.io/smkent/paperless-ngx:feature-oidc-allauth

Oh... well that would have been easier to use ;-)

setupwise if it were merged you pretty much only need the four OIDC environment variables (hide login being optional). Setup in Authelia (don't know about others) was easy. The redirect_url follows some kind of logic, that would need to be documented. That the redirect URL is https is fine by me, everything should be https anyways also locally.

What also would need to be documented is how user mapping is done. Luckily my paperless-user as well as the authelia-user are the same, but I wouldn't know what OIDC property is used for that (e.g. openid.sub, email.email, profile.username). In my case it just works, but for production this would need to be made clear, i.e. "your profile.username needs to be the same as your paperless username" or something like that.

DennisGaida avatar Feb 09 '23 15:02 DennisGaida

There's also the pre-built image ghcr.io/smkent/paperless-ngx:feature-oidc-allauth

@DennisGaida That's detailed setup, thanks. I may be able to document setup on this side enough with those.

this image works perfect.

lazyzyf avatar Feb 09 '23 16:02 lazyzyf

Codecov Report

Merging #1746 (54542b1) into dev (40db244) will decrease coverage by 0.91%. The diff coverage is 29.88%.

@@            Coverage Diff             @@
##              dev    #1746      +/-   ##
==========================================
- Coverage   93.11%   92.20%   -0.91%     
==========================================
  Files         140      142       +2     
  Lines        5997     6083      +86     
==========================================
+ Hits         5584     5609      +25     
- Misses        413      474      +61     
Flag Coverage Δ
backend 92.20% <29.88%> (-0.91%) :arrow_down:

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/paperless/allauth_custom.py 0.00% <0.00%> (ø)
src/paperless/urls.py 100.00% <ø> (ø)
src/paperless/settings.py 77.63% <43.75%> (-6.42%) :arrow_down:
src/documents/templatetags/django_settings.py 83.33% <83.33%> (ø)

:mega: We’re building smart automated test selection to slash your CI/CD build times. Learn more

codecov[bot] avatar Feb 09 '23 22:02 codecov[bot]

One thing I have noticed:

  1. A new user is created for the OIDC user
  2. OIDC breaks internal authentication

I now have two users: "user" and "user8". user8 represents the OIDC user. the 8 seems to be some kind of autoincrement. When I delete the user in sqlite in auth_user as well as in socialaccount_socialaccount, and repeat the OIDC process I get a new user with a new "id", e.g. user7. With the OIDC changes enabled I can log in via OIDC - I can not login with the original user using regular login (wrong password).

Checking the DB logs (I added 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG'}, in line 630 in settings.py), I see that my useraccount is queried and found, but somehow a new user is created. Maybe this is the default and is as it is supposed to be? A new user means new UI settings and views, and of course I only want one user.

I got around the duplicate user issue now by manually editing the socialaccount_socialaccount table, setting the socialaccount_socialaccount.user_id to my original user id (of "user"). I can now login via OIDC and I am the real user. I can also access the django admin panel like that. The other use that was created kind of is an orphan now and I just went ahead and deleted it... this of course shouldn't be the process and the socialaccount should just have been linked I suppose?

Once I disable the OIDC changes, I can login again with the original user.

Of course I'd love just one user and for both authentication methods to work at the same time.

DennisGaida avatar Feb 10 '23 09:02 DennisGaida

@stumpylog what kind of documentation do you have in mind? For people with authelia and oidc already set up, it's just as easy as setting 4 variables and using paperless with https. Only documentation for the variables would be needed and a warning about the redirect uri https issue.

Oidc in general really only makes sense to set up if you have a lot of services to log in to. Providing a complete setup for keycloak or authelia with all the security considerations will be a long journey. These services also already have their own documentation available on how to integrate common apps, see https://www.authelia.com/integration/openid-connect/. Including paperless in there would also be an option.

hendrik1120 avatar Feb 10 '23 17:02 hendrik1120