-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Feature: Add OpenID Connect SSO support via django-allauth #1746
Conversation
Hello @smkent, thank you very much for submitting this PR to us! This is what will happen next:
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. |
Pull Request Test Coverage Report for Build 3909301335
💛 - 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 👏 |
Looking forward for this PR being approved! |
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/ |
please merge! Using a central auth is an essential feature in my opinion |
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. |
I'm waiting the merge because it's a really useful feature for my usecase ! |
this image works perfect. |
Codecov Report
@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
One thing I have noticed:
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 Checking the DB logs (I added I got around the duplicate user issue now by manually editing the 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably this is a misconfiguration on my part (but goes to show the need for good documentation on this feature), but I'm not able to get it working with Authelia. Attempting to log in using OIDC spins for sometime, before timing out.
@stumpylog what kind of documentation do you have in mind? Oidc in general really only makes sense to set up if you have a lot of services to log in to. |
Well, I already use Authelia and my own domain for https, but setting the 4 variables merely leads to timeouts. So I don't find it quite that easy and that's why I feel some additional documentation is still needed, or else there will be lots of reports to deal with. And clearly, I don't do web stuff often enough to deal with those. |
Hello! First of all, I was able to get it working with Google OIDC. (I don't have Authelia avaiable, and don't really need it.) Here are a couple issues I noticed:
|
this would be a great feature! |
Why is this PR marked as draft now? |
Because it needs more work. We know folks are eager for the feature to be released, please limit comments to those that are useful to that goal. Thanks all. |
What DennisGaida noted indirectly touches on this, but something that doesn't seem to have been addressed yet is exactly how this interacts with existing users, and what OIDC claims are used when pulling the user from the provider. A common flow is to use a user configurable claim (often 'prefered_username' or 'email') that is used when loading the user information from the provider. Then, if that field matches one from an existing user, that user is signed-in, if not, a new user is created with the info from the token or user info endpoint. Alternatively, some implementations use the 'sub' claim as an ID to tie users in the local database to OIDC users and allow admins to change this value for any given user so that deployments can easily migrate their existing users over to OIDC. An unmatched subject means a new user. The more flexible this is the better, but at the very least how it's currently handled should be documented. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Using AuthentikI see the discussion about working with existing accounts and getting more information on the configuration values, so here's my experience using Authentik. Regarding linking existing accountsIn Authentik I created an oAuth2 provider. Under "Advanced protocol settings" there's an option called When I originally set up PaperlessNgx with the base image, I registered with the same username I use for Authentik, hoping the Proxy auth example from PaperlessNg would still work-- it doesn't seem to, so thanks for making this feature! Now then, I set up my provider to have its I deleted the new user records from The email address matched the one on my base Paperless account but still created a new user. Ultimately I linked my Authentik login to my existing account by hand. I deleted the I also tried with and without these environment variables which I may have interpreted wrong from the README:
My environment variables for Authentik
The URL is the value of Authentik Provider settings
The Hope this helps with moving this feature forward! |
Maybe can sync group from OIDC. We know some IDP can offer group information. like this (These code is writen for other project) def authenticate_user(self, user_info):
if "email" in user_info:
User = get_user_model()
user, created = User.objects.get_or_create(email=user_info["email"])
user = self.__user_id_sync(user,user_info)
if created or settings.OIDC_USERINFO_SYNC:
user = self.__user_info_sync(user,user_info)
user.save()
user.backend = "django.contrib.auth.backends.ModelBackend"
return user
messages.error(
self.request,
_(
"Cannot get your email address. Did you try to"
" log in with the correct account?"
),
)
return redirect("login_form")
def __user_info_sync(self,user,user_info):
if "name" in user_info:
name = user_info["name"].split(" ")
size = len(name)
if size == 0:
first_name = "user"
second_name = ""
elif len(name) == 1:
first_name = name[0]
second_name = ""
else:
first_name = name[0]
second_name = name[1]
user.first_name = first_name
user.last_name = second_name
user.save()
return user
def __user_id_sync(self,user, user_info):
role = self.__check_role(user_info)
user.role = role
try:
user.username = user_info["sub"]
except:
pass
user.save()
return user
def __check_role(self, user_info):
oidc_roles = self.__get_oidc_roles(user_info)
if len(oidc_roles) == 0:
return settings.OIDC_ROLE_DEFAULT
return self.__analyze_role(oidc_roles)
def __get_oidc_roles(self, user_info):
tmp = user_info
for path in settings.OIDC_ROLE_PATH_IN_RETURN:
path = path.strip(",")
if path == "":
continue
try:
tmp = tmp[path]
except KeyError:
return []
if isinstance(tmp, list):
return tmp
elif isinstance(tmp, str):
return [tmp]
else:
return []
def __analyze_role(self, oidc_roles):
ROLE_ADMIN_NAME = "admin"
ROLE_MANAGE_NAME = "manage"
role_map = {
ROLE_ADMIN_NAME: settings.OIDC_ROLE_ADMIN_PATTEREN,
ROLE_MANAGE_NAME: settings.OIDC_ROLE_MANAGE_PATTEREN,
}
roles = []
for key in role_map.keys():
role_name = role_map[key]
role_name = rf"{role_name}"
if role_name.strip() == "":
continue
for oidc_role in oidc_roles:
if re.search(role_name, oidc_role):
roles.append(key)
break
if ROLE_ADMIN_NAME in roles:
return 1
elif ROLE_MANAGE_NAME in roles:
return 2
else:
return settings.OIDC_ROLE_DEFAULT |
@smkent its been many months with no activity so we’re going to close this. Again, I think others are interested in this feature so we hope to hear from you (or anyone else who is interested in moving this forward) in the future. Thank you for the contribution |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. |
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:
With no SSO configured, this PR does not change the login behavior:
However, SSO options will now appear on the login screen when configured:
If SSO is configured, it is also possible to hide the password form via a new setting
PAPERLESS_LOGIN_HIDE_PASSWORD_FORM
:Type of change
Checklist:
pre-commit
hooks, see documentation.