15five / django-scim2 Goto Github PK
View Code? Open in Web Editor NEWA SCIM 2.0 Service Provider Implementation (for Django)
Home Page: http://django-scim2.readthedocs.io/
License: Other
A SCIM 2.0 Service Provider Implementation (for Django)
Home Page: http://django-scim2.readthedocs.io/
License: Other
I have requirements such that I am not allowed to do any migrations to my default user model, so I extended my user model to a new SCIM_USER_MODEL by considering User model with one to one relation. now in settings file when I am mentioning the USER_MODEL_GETTER it is saying "Manager isn't accessible via SCIM_USER_MODEL instances". Please help me on this
Is your feature request related to a problem? Please describe.
Currently for detect user model, the django-scim2
use get_user_model
from the Django
builtin function,
And get_user_model
of Django
, use settings.AUTH_USER_MODEL
user = get_user_model().objects.get(id=1)
scim_user = SCIMUser(user)
But if you want to use another defined model for user of scim that is related to the customized user model of settings.AUTH_USER_MODEL
, there is no settings for doing that. we should override all related files as urls
, views
, filter
and adapter
.
Describe the solution you'd like
Same as get_user_model
that get group model from the scim_settings
settings:
def get_group_model():
"""
Return the group model.
"""
return scim_settings.GROUP_MODEL
SCIM_SERVICE_PROVIDER = {
'GROUP_MODEL': 'accounts.models.Group',
}
Implement it as:
def get_user_model():
"""
Return the user model.
"""
return scim_settings.USER_MODEL
SCIM_SERVICE_PROVIDER = {
'USER_MODEL': 'accounts.models.UserSCIM',
}
Describe alternatives you've considered
Override model_cls_getter
of UsersView
and UserFilterQuery, and
SCIMUser.handle_add `
Describe the bug
externalId is not stored in User when creating a new user via POST method and not returned when retrieving a user via GET.
To Reproduce
curl -H "Authorization: Bearer xxxxx" "http://localhost:8000/scim/v2/Users" -d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"externalId":"0a21f0f2-8d2a-4f8e-bf98-7363c4aed4ef","userName":"Test_User_ab6490ee-1e48-479e-a20b-2d77186b5dd1","active":true,"emails":[{"primary":true,"type":"work","value":"[email protected]"}],"meta":{"resourceType":"User"},"name":{"formatted":"givenName familyName","familyName":"familyName","givenName":"givenName"},"roles":[]}' -X POST
curl -H "Authorization: Bearer xxxxxx" "http://localhost:8000/scim/v2/Users/14"
Expected behavior
externalId should be stored in model and returned when retrieving a user.
Would love to sponsor this project. ππ
After the user created, I want to put some log for tracking
I wrote something like this
class PostView(object):
def post(self, request, **kwargs):
obj = self.model_cls()
scim_obj = self.scim_adapter(obj, request=request)
body = json.loads(request.body.decode(constants.ENCODING))
scim_obj.from_dict(body)
try:
scim_obj.save()
if self.post_post: #
self.post_post(scim_obj) #
Describe the bug
When doing an API call to GET /scim/v2/Users, getting a Cannot resolve keyword 'scim_id' into field.
Looks like the user model does not go through the SCIMUSer adapter. any ideas?
Using
python 3.7
django 2.1.7
Stacktrace
2019-06-05T10:18:43 local app: ERROR /venv/app/lib/python3.7/site-packages/django_scim/views.py func:dispatch line:94 - Unable to complete SCIM call.
Traceback (most recent call last):
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 91, in dispatch
return super(SCIMView, self).dispatch(request, *args, **kwargs)
File "/venv/app/lib/python3.7/site-packages/django/views/generic/base.py", line 88, in dispatch
return handler(request, *args, **kwargs)
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 257, in get
return self.get_many(request)
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 281, in get_many
return self._build_response(request, qs, *self._page(request))
File "/venv/app/lib/python3.7/site-packages/django_scim/views.py", line 201, in _build_response
total_count = sum(1 for _ in qs)
File "/venv/app/lib/python3.7/site-packages/django/db/models/query.py", line 268, in iter
self._fetch_all()
File "/venv/app/lib/python3.7/site-packages/django/db/models/query.py", line 1186, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/venv/app/lib/python3.7/site-packages/django/db/models/query.py", line 54, in iter
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1052, in execute_sql
sql, params = self.as_sql()
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 449, in as_sql
extra_select, order_by, group_by = self.pre_sql_setup()
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 51, in pre_sql_setup
order_by = self.get_order_by()
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 315, in get_order_by
field, self.query.get_meta(), default_order=asc))
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 668, in find_ordering_name
field, targets, alias, joins, path, opts, transform_function = self._setup_joins(pieces, opts, alias)
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 698, in _setup_joins
field, targets, opts, joins, path, transform_function = self.query.setup_joins(pieces, opts, alias)
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1473, in setup_joins
names[:pivot], opts, allow_many, fail_on_missing=True,
File "/venv/app/lib/python3.7/site-packages/django/db/models/sql/query.py", line 1389, in names_to_path
"Choices are: %s" % (name, ", ".join(available)))
django.core.exceptions.FieldError: Cannot resolve keyword 'scim_id' into field. Choices are: auth_token, date_joined, email, first_name, groups, id, is_active, is_staff, is_superuser, last_login, last_name, logentry, oauth2_provider_accesstoken, oauth2_provider_application, oauth2_provider_grant, oauth2_provider_refreshtoken, password, user_permissions, user_settings, username, appcomments, appuser
2019-06-05T10:18:43 local app: ERROR /venv/app/lib/python3.7/site-packages/django/utils/log.py func:log_response line:228 - Internal Server Error: /api/scim/v2/Users
[05/Jun/2019 10:18:44] "GET /api/scim/v2/Users HTTP/1.1" 500 289
Add documentation on how a custom schema for custom object types can be integrated (can they?) with django-scim2.
Describe the bug
The following error occurred when accessing a SCIM API in Django 2.x.
Internal Server Error: /
Traceback (most recent call last):
File "/home/tomatsue/.local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/home/tomatsue/.local/lib/python3.6/site-packages/django_scim/middleware.py", line 20, in __call__
self.process_request(request)
File "/home/tomatsue/.local/lib/python3.6/site-packages/django_scim/middleware.py", line 27, in process_request
if not hasattr(request, 'user') or request.user.is_anonymous():
TypeError: 'bool' object is not callable
A bool value is called here.
django-scim2/django_scim/middleware.py
Line 27 in 426e7ba
According to Django 1.10: django.contrib.auth.models.User.is_anonymous, is_anonymous
is no longer a method.
Changed in Django 1.10:
In older versions, this was a method. Backwards-compatibility support for using it as a method will be removed in Django 2.0.
To Reproduce
Run curl -v localhost:8000/scim/v2/curl
in Django 2.x.
Expected behavior
Check if the user is anonymous without the error.
Stacktrace
Described in above Describe the bug.
Additional context
Nothing.
Describe the bug
Project startup with 0.11.1
fails with FileNotFoundError: [Errno 2] No such file or directory: '/virtualenv/lib/python3.6/site-packages/django_scim/schemas/core'
error.
0.11.0
works.
To Reproduce
Install version 0.11.1
from pypi, follow setup instructions and try running django.
Expected behavior
Starts without errors.
Stacktrace
Traceback (most recent call last):
File "src/manage.py", line 10, in <module>
execute_from_command_line(sys.argv)
File "/virtualenv/lib/python3.6/site-packages/django/core/management/__init__.py", line 364, in execute_from_command_line
utility.execute()
File "/virtualenv/lib/python3.6/site-packages/django/core/management/__init__.py", line 356, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 283, in run_from_argv
self.execute(*args, **cmd_options)
File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 327, in execute
self.check()
File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 359, in check
include_deployment_checks=include_deployment_checks,
File "/virtualenv/lib/python3.6/site-packages/django/core/management/commands/migrate.py", line 62, in _run_checks
issues.extend(super(Command, self)._run_checks(**kwargs))
File "/virtualenv/lib/python3.6/site-packages/django/core/management/base.py", line 346, in _run_checks
return checks.run_checks(**kwargs)
File "/virtualenv/lib/python3.6/site-packages/django/core/checks/registry.py", line 81, in run_checks
new_errors = check(app_configs=app_configs)
File "/virtualenv/lib/python3.6/site-packages/django/core/checks/urls.py", line 16, in check_url_config
return check_resolver(resolver)
File "/virtualenv/lib/python3.6/site-packages/django/core/checks/urls.py", line 26, in check_resolver
return check_method()
File "/virtualenv/lib/python3.6/site-packages/django/urls/resolvers.py", line 256, in check
for pattern in self.url_patterns:
File "/virtualenv/lib/python3.6/site-packages/django/utils/functional.py", line 35, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/virtualenv/lib/python3.6/site-packages/django/urls/resolvers.py", line 407, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/virtualenv/lib/python3.6/site-packages/django/utils/functional.py", line 35, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/virtualenv/lib/python3.6/site-packages/django/urls/resolvers.py", line 400, in urlconf_module
return import_module(self.urlconf_name)
File "/virtualenv/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/myproject/urls.py", line 133, in <module>
url(r'^scim/v2/', include('django_scim.urls', namespace='scim')),
File "/virtualenv/lib/python3.6/site-packages/django/conf/urls/__init__.py", line 50, in include
urlconf_module = import_module(urlconf_module)
File "/virtualenv/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/virtualenv/lib/python3.6/site-packages/django_scim/urls.py", line 6, in <module>
from . import views
File "/virtualenv/lib/python3.6/site-packages/django_scim/views.py", line 423, in <module>
class SchemasView(SCIMView):
File "/virtualenv/lib/python3.6/site-packages/django_scim/views.py", line 427, in SchemasView
schemas_by_uri = {s['id']: s for s in get_all_schemas_getter()()}
File "/virtualenv/lib/python3.6/site-packages/django_scim/utils.py", line 63, in get_all_schemas_getter
return scim_settings.SCHEMAS_GETTER
File "/virtualenv/lib/python3.6/site-packages/django_scim/settings.py", line 120, in __getattr__
val = perform_import(val, attr)
File "/virtualenv/lib/python3.6/site-packages/django_scim/settings.py", line 73, in perform_import
return import_from_string(val, setting_name)
File "/virtualenv/lib/python3.6/site-packages/django_scim/settings.py", line 86, in import_from_string
module = importlib.import_module(module_path)
File "/virtualenv/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "/virtualenv/lib/python3.6/site-packages/django_scim/schemas/__init__.py", line 22, in <module>
ALL = load_schemas()
File "/virtualenv/lib/python3.6/site-packages/django_scim/schemas/__init__.py", line 13, in load_schemas
files = os.listdir(sub_dir)
FileNotFoundError: [Errno 2] No such file or directory: '/virtualenv/lib/python3.6/site-packages/django_scim/schemas/core'
Additional context
This is probably due to the static files for the schemas
missing in the packaged version. Probably since the move of code into the src
sub-directory. The path needs to be changed in MANIFEST.in
as well.
Fixed in #28
While trying to use django-scim2
in a project, I noticed, that the default UserFilterQuery
uses a custom SQL query building class under the hood. This is also the case for the default GroupFilterParser
. It could also very well be that I overlooked something.
Anyway, I tried to work around this by using a custom filter parsers, would like to share what I tried, and could currently see no better option than filing an issue here π.
The custom parsers look something like this (everything that was needed seemed to be already there in the scim2_filter_parser
):
from scim2_filter_parser.lexer import SCIMLexer
from scim2_filter_parser.parser import SCIMParser
from scim2_filter_parser.transpilers.django_q_object import Transpiler as SCIMTranspiler
class CustomFilterParserBase:
attr_map = {}
@classmethod
def search(cls, query, request=None):
return cls.query_to_qs(query, cls.attr_map)
@staticmethod
def query_to_qs(query, attr_map):
tokens = SCIMLexer().tokenize(query)
ast = SCIMParser().parse(tokens)
qs = SCIMTranspiler(attr_map).transpile(ast)
return qs
class UserFilterParser(CustomFilterParserBase):
attr_map = {
# attr, sub attr, uri
("externalId", None, None): "userlicense__external_user_id",
("userName", None, None): "username",
("name", "familyName", None): "last_name",
("familyName", None, None): "last_name",
("name", "givenName", None): "first_name",
("givenName", None, None): "first_name",
("active", None, None): "is_active",
}
class GroupFilterParser(CustomFilterParserBase):
attr_map = {
# attr, sub attr, uri
("externalId", None, None): "external_group_id",
("displayName", None, None): "name",
# ("userName", None, None): "username",
# ("name", "familyName", None): "last_name",
# ("familyName", None, None): "last_name",
# ("name", "givenName", None): "first_name",
# ("givenName", None, None): "first_name",
# ("active", None, None): "is_active",
}
I set them in the SCIM_SERVICE_PROVIDER
configuration within settings.py
like this:
SCIM_SERVICE_PROVIDER = {
# ...
"USER_FILTER_PARSER": "my_scim.filters.UserFilterParser",
"GROUP_FILTER_PARSER": "my_scim.filters.GroupFilterParser",
# ...
}
And I have a custom UsersView
and GroupsView
that look something like this:
import json
from django_scim import constants
from django_scim import exceptions as scim_exceptions
from django_scim import views
from scim2_filter_parser.parser import SCIMParserError
class CustomFilterMixin:
def _search(self, request, query, start, count):
qs = self.model_cls.objects.all()
try:
q = self.__class__.parser_getter().search(query, request)
except (ValueError, SCIMParserError) as e:
raise scim_exceptions.BadRequestError(
"Invalid filter/search query: " + str(e)
)
else:
qs = qs.filter(q)
extra_filter_kwargs = self.get_extra_filter_kwargs(request)
qs = qs.filter(**extra_filter_kwargs)
extra_exclude_kwargs = self.get_extra_exclude_kwargs(request)
qs = qs.exclude(**extra_exclude_kwargs)
qs = self.get_queryset_post_processor(request, qs)
qs = qs.order_by(self.lookup_field)
return self._build_response(request, qs, *self._page(request))
class UsersView(CustomFilterMixin, views.UsersView):
pass
class GroupsView(CustomFilterMixin, views.GroupsView):
pass
I hope that this can be of use and If you have any more question, please let me know.
The right way to use
v 0.3.0
from six.moves.urllib.parse import urljoin
or
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
the same with
try:
import unittest.mock as mock
except ImportError:
import mock
I tried fixing a few things and running on our MySQL implementation, but I'm getting a is an invalid keyword argument for this function
error that's stumping me at the moment, and I wonder if this is a real limitation or just a "we never tried that" situation.
Going over the code, there seems to be no support for the .search format as defined in http://www.simplecloud.info/. Instead, we need to POST for example to /Groups/.search which is ok, but there's no documentation on the expected format. We got as far as figuring out that this should work:
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:SearchRequest"], "filter": "displayName eq \"group-name\"" }
...but alas I get a blank list as my only result. Is there some documentation about this aspect, or at least a sample query that we can use to get started?
(And thanks for this terrific piece of software!)
Describe the bug
PATCH request to update user attributes results in 500 error.
To Reproduce
curl -H "Authorization: Bearer xxxxxx" "http://localhost:8000/scim/v2/Users/14" -d '{"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations": [{"op": "Replace","path": "name.familyName","value": "updatedFamilyName"}]}' -X PATCH
Expected behavior
User's attribute should be updated and user should be returned.
Stacktrace
Traceback (most recent call last):
File "/xxxxxxxxxx/django_scim/views.py", line 91, in dispatch
return super(SCIMView, self).dispatch(request, *args, **kwargs)
File "/xxxxxxxxxx/django/views/generic/base.py", line 88, in dispatch
return handler(request, *args, **kwargs)
File "/xxxxxxxxxx/django_scim/views.py", line 336, in patch
scim_obj.handle_operations(operations)
File "/xxxxxxxxxx/django_scim/adapters.py", line 87, in handle_operations
handler(path, value, operation)
File "/xxxxxxxxxx/django_scim/adapters.py", line 270, in handle_replace
for attr, attr_value in attrs.items():
AttributeError: 'unicode' object has no attribute 'items'
I built the environment as follows.
Doing some tests with curl, I was able to get information of ServiceProviderConfig, ResourceTypes, and Schemas with the GET method.
(I also confirmed that errors occur when there is no bearer token.)
However, when I tried to create a User with POST, scim_id was duplicated.
So I overwrite scim_id in models.py to comment-out unique=True (change to unique=False), I was able to create a user.
I soon found out why this happened.
Attempting to get the user with scim_id=21 with GET /scim/v2/Users/21 returned an error that multiple users were detected.
And looking at admin's scim_id, it changed to 21.
I changed admin's scim_id to 1 and tried creating another user again, and admin's scim_id is changed to 22 which is the same as the new user.
Considering the timing, I think that there is a problem with save (L368) of PostView Class in views.py below:
https://github.com/15five/django-scim2/blob/master/src/django_scim/views.py#L368
I overwrite "save" method by referring to "demo/app/adapters.py".
def save(self):
is_new_user = self.is_new_user
try:
with transaction.atomic():
super().save()
if is_new_user:
# Set SCIM ID to be equal to database ID. Because users are uniquely identified with this value
# its critical that changes to this line are well considered before executed.
self.obj.__class__.objects.update(scim_id=str(self.obj.id))
logger.info(f'User saved. User id {self.obj.id}')
except Exception as e:
# raise self.reformat_exception(e)
raise RuntimeError(e.args) from e
I think that there is a problem with the below URL.
https://github.com/15five/django-scim2/blob/master/src/django_scim/models.py#L150-L153
Or is there a problem with the save overriding or the method of issuing Bearer Tokens?
Describe the bug
when using PATCH
to update a record it fails with NotImplementedError
when updating a boolean field.
To Reproduce
Try to PATCH
a User
Resource with this:
{
"schemas":
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{
"op":"replace",
"path":"active",
"value": true
}]
}
Expected behaviour
Can alter fields with boolean values.
Additional context
I suppose we should support bool
(and int
, float
?) here and here
Support SCIM 1.1 Implementations
http://www.simplecloud.info/#Specification
Many Service providers currently used SCIM 1.1.
This library may be useful for development to support version 1.1.
login_requried was added to SCIMView.dispatch
in f791a44, marking all requests to the SCIM views required for authentication.
This ties with authentication based on the request.user
object added by Django's AuthenticationMiddleware
, therefore requiring us to actually have a User
object representing an authenticated client.
This is not necessarily the case when there isn't a corresponding AUTH_USER_MODEL
that can be associated with the SCIM client. login_required
ensures the presence of user
which is_authenticated
, but it does not have anything to do directly with the views themselves.
A current workaround is to create a class preventing to be AUTH_USER_MODEL
, that implements is_authenticated
. This breaks type-safety and Django's expectation for request.user
. But it would be better if django-scim2 actually supports custom authentication methods for the SCIM views.
Additional context can be found here.
Describe the bug
django-scim2 is incompatible with django v4.0 which was released Dec 7, 2021.
The code in src/django_scim/models.py
is using the API ugettext_lazy
(see line 5).
This API was deprecated in django v3.0 (Release notes) and should be replaced with gettext_lazy()
.
The same function is used in demo/app/models.py
in line 4.
Describe the bug
The code sorts the results based on the lookup_field
,
and there is no way to sort the results based on any other field, especially the modified
field.
def get_many(self, request):
query = request.GET.get('filter')
if query:
return self._search(request, query, *self._page(request))
extra_filter_kwargs = self.get_extra_filter_kwargs(request)
extra_exclude_kwargs = self.get_extra_exclude_kwargs(request)
qs = self.model_cls.objects.filter(
**extra_filter_kwargs
).exclude(
**extra_exclude_kwargs
)
qs = self.get_queryset_post_processor(request, qs)
qs = qs.order_by(self.lookup_field)
return self._build_response(request, qs, *self._page(request))
To Reproduce
Steps to reproduce the behavior...
Expected behavior
We expect to be able to sort the results based on any field, especially the modified
field.
Additional context
By swapping these two lines of code, the ordering of the queryset by lookup_field
will be performed before applying any custom filtering or modifications via get_queryset_post_processor()
,
So the solution is to replace the following code:
qs = self.get_queryset_post_processor(request, qs)
qs = qs.order_by(self.lookup_field)
with this code:
qs = qs.order_by(self.lookup_field)
qs = self.get_queryset_post_processor(request, qs)
Describe the bug
adapters.py:SCIMUser.handle_replace
(and SCIMGroup
as well) explicitly calls self.obj.save()
instead of the SCIMUser.save()
(SCIMGroup.save()
respectively)
Expected behavior
After calling the handle_operations
in views.py:PatchView.patch
call the save()
method of SCIMUser
(SCIMGroup
).
Additional context
I need to perform checks and execute code whenever a user object is saved.
I just updated from 0.5.* to the latest 0.10.6 and noticed I consistently get a 500 error () when loading /Users and /Groups:
unbound method get_user_model() must be called with UsersView instance as first argument (got nothing instead)
We use Python 2.7 and Django 1.11, and at no point the stack trace shows our internal code:
ERROR 2019-04-14 13:26:16,237 views 31136 139791130908416 Unable to complete SCIM call.
Traceback (most recent call last):
File "[...]/site-packages/django_scim/views.py", line 90, in dispatch
return super(SCIMView, self).dispatch(request, *args, **kwargs)
File "[...]/site-packages/django/views/generic/base.py", line 88, in dispatch
return handler(request, *args, **kwargs)
File "[...]/site-packages/django_scim/views.py", line 246, in get
return self.get_many(request)
File "[...]/site-packages/django_scim/views.py", line 262, in get_many
extra_filter_kwargs = self.get_extra_filter_kwargs(request)
File "[...]/site-packages/django_scim/views.py", line 47, in get_extra_filter_kwargs
return get_extra_model_filter_kwargs_getter(self.model_cls)
File "[...]/site-packages/django_scim/views.py", line 43, in model_cls
return self.class.model_cls_getter()
TypeError: unbound method get_user_model() must be called with UsersView instance as first argument (got nothing instead)
[2019/04/14 13:26:16] HTTP GET /scim/v2/Users 500 [0.34, 127.0.0.1:42578]
Is this perhaps an issue for Python2 only?
Is your feature request related to a problem? Please describe.
I'm delighted to find django-scim2 and am excited to use it to connect to AzureAD.
I am not sure how to fix this error: AttributeError: 'User' object has no attribute 'scim_id'.
I don't think I can copy the User implementation in the demo:
class User(AbstractSCIMUser, TimeStampedModel, AbstractBaseUser):
as django documentation warns against implementing a custom user model mid project and I haven't implemented a custom user model from the beginning.
Describe the solution you'd like
I'd like to implement a 'profile style' model to store my scim related data in, like so:
class UserScim(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'user_scim')
scim_id = models.CharField(
_('SCIM ID'),
max_length=254,
null=True,
blank=True,
default=None,
unique=True,
help_text=_('A unique identifier for a SCIM resource as defined by the service provider.'),
)
scim_external_id = models.CharField(
_('SCIM External ID'),
max_length=254,
null=True,
blank=True,
default=None,
db_index=True,
help_text=_('A string that is an identifier for the resource as defined by the provisioning client.'),
)
def __str__(self):
return self.user.username + ' ' + str(self.scim_id)
Would this decoupling work? What would I need to update elsewhere?
Thank you very much.
The get_nested_field() function by @pablodiazgtierrez is a really nice addition to this library, but doesn't seem to work for reverse relationships.
I have the same use case as Pablo (needing to filter by company in a multitennant SAAS). However in my use case the object relationship direction of the object is a reverse one, (Example Model below).
I'm using this line in get_extra_filter_kwargs:
def get_extra_filter_kwargs(request, *args, **kwargs):
return {
'companies__company': request.user.memberships.first().company,
}
Similar to Pablo's original issue, for requests to /Users?filter=userName+eq+"example_username" the result is blank, but /Users/1 and Users?attributes=userName do work correctly, and apply the company restrictions as expected.
I think I've diagnosed that the hasattr() line (shown below) that was added in this merge is causing this the filtering to stop prematurely.
if not hasattr(obj, field_name):
return None
Example Model
class UserInCompany(models.Model):
company = models.ForeignKey(Company, related_name = 'company_members', on_delete=models.CASCADE, null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name = 'memberships', null=True, blank=True)
class Company(models.Model):
company_name = models.CharField(max_length=200, null=True, blank=True)
Originally posted by @William-Wildridge in #48 (comment)
Hi,
When I do the provisioning from my AD the group members are not been saved. I get all the groups and users but the groups have no users in it.
First of all, thank you for putting out this excellent library. We are so glad to have it around. We are using it to interact with Microsoft's AAD user provisioning system, and so far it has served us well.
Now, we've heard from Microsoft staff that they're adding some tests and that we're failing them. Specifically, they ask why when they query /Users?filter=userName+eq+"[email protected]" the result is blank (even though they can query /Users/USERID successfully for the same user).
I will point to them that, as explained in /ServiceProviderConfig, filtering is not supported. However, in the code it says that there's partial support. So I'm wondering:
What's the extent of this support?
How can we help extend this support to include the query above?
Edit: I should have labeled this issue a "question" or "help wanted", but I didn't see the option. Sorry!
I was wondering if there is a way to handle re-creation of users.
When a user gets created, removed (set to inactive), and created again, django will throw the exception that the user already exists. I`m not sure what is the safest way to implement that.
Also, is there a way to limit which user (type) can execute the scim api calls?
And thanks for the library, it saves me tons of work!
Hi,
I'm not able to access the SCIM-endpoints while using a Bearer token (OAuth 2) or using Basic Authentication, while all my other endpoints (other than SCIM) do allow this.
I've figured out that my requests to the SCIM endpoints do succeed when I provide a Cookie-header in my request, with references to the csrf-token and sessionid. If I don't do this, I always get a 401 - Unauthorized, even if I provide a Bearer token or login credentials.
I have included your middleware after the authentication-middleware as instructed in your documentation, but I can't seem to use the SCIM-endpoints without providing a Cookie-header in my request.
Is this expected behavior or did I configure something wrong?
This is a portion of my settings.py file:
`INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'users',
'api',
'corsheaders',
'django_filters',
'django_scim',
'oauth2_provider'
]
SCIM_SERVICE_PROVIDER = {
'NETLOC': 'localhost',
'AUTHENTICATION_SCHEMES': [
{
'type': 'oauth2',
'name': 'OAuth 2',
'description': 'Oauth 2 implemented with bearer token',
'primary': 'true'
},
{
"name": "HTTP Basic",
"description": "Authentication scheme using the HTTP Basic Standard",
"type": "httpbasic"
}
],
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_scim.middleware.SCIMAuthCheckMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.BasicAuthentication',
),
}`
get_queryset_post_processor
is not applied to querysets in _search
. Not sure if this is a bug or there is some reason for this, but it seems odd that the queryset_post_processor
is applied to the queryset for list requests, but not when a filter is passed.
E.g., I have a queryset_post_processor which returns qs.filter(username__startswith='test')
, and my queryset consists of two users, id 1 has username testuser
and id 2 has username nottestuser
. If I make a GET /Users
request, only user 1 is returned. However, if I make a GET /Users/?filter=userName eq "nottestuser"
, user 2 is returned. I expected the behaviour to be for the 2nd request to not return any result, as filtering the request should return a subset of the results obtained in the request with no filters.
I don't know why it is asking for user credentials v2/scim/Users how to get credentials I am giving my admin credentials it is saying unauthorized
Hi,
I'm implementing your package into my project and I can add users to my database using the scim/v2/Users endpoint. However, each time I send a POST-request, the user does get created in my database but I receive a 501 status-code, instead of a 201 - Created status.
I also get the same status code whenever I try to retrieve a specific user with its ID.
Can't seem to figure out why this is happening.
This is an example of the message in the request's body:
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "[email protected]",
"password": "test123",
"emails": [
{
"value": "[email protected]",
"type": "work"
}
],
"externalId": "abanderas",
"name": {
"familyName": "Banderas",
"givenName": "Antonio"
}
}
Really need to move to pytest; so that usage of things like coverage (amongst other things) has a greater level of community support.
Describe the bug
ModuleNotFoundError: No module named 'django_scim'
To Reproduce
Add django_scim
Django (cookiecibtter.
Expected behavior
django_scim2
module to be found and docker to complete spin-up.
> $ pip list -V | grep django-scim2 [Β±main β]
django-scim2 0.17.3
Addded django_scim2
to requirements.txt
django-scim2==0.17.3 # https://github.com/15five/django-scim2
Describe the bug
In the adapter the ATTR_MAP is set to the attr_map of the UserFilterQuery objects:
ATTR_MAP = filters.UserFilterQuery.attr_map
There is however a setting where the UserFilterQuery object can be overwritten by a custom object.
Expected behavior
The object specified in the USER_FILTER_PARSER
setting should be used. This can be achieved by using utils.get_user_filter_parser
(see here).
I am getting the unauthorized issues
on scim
endpoints I know authentication and authorization is not implemented on django-scim. That is why I integrated Django OAuth Toolkit.
But it didn't work out. I tested demo project, but I am getting the same issue.
Describe the bug
Unauthorized: /scim/v2/Users
"GET /scim/v2/Users HTTP/1.1" 401 0
Expected behavior
I want to retrieve /scim
endpoint with Bearer token.
The SCIMView.dispatch
method:
@method_decorator(csrf_exempt)
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not self.implemented:
return self.status_501(request, *args, **kwargs)
try:
return super(SCIMView, self).dispatch(request, *args, **kwargs)
except Exception as e:
if not isinstance(e, exceptions.SCIMException):
logger.exception('Unable to complete SCIM call.')
e = exceptions.SCIMException(str(e))
content = json.dumps(e.to_dict())
return HttpResponse(content=content,
content_type=constants.SCIM_CONTENT_TYPE,
status=e.status)
converts any exception into a string and puts it in the HttpResponse. I think ideally there'd be a setting allowing library users to opt out of this behavior (or possibly even better - this behavior should be disabled by default and require explicitly enabling it) and use a generic message such as Exception occurred while processing the SCIM request
. The reason being that while this is very useful for debugging, it can be dangerous in production - sensitive information can be revealed through str(e)
and the SCIM client may not always be a party so trusted that revealing information would be harmless.
As a simple example if you deploy some bug to production where some_dict[secret_string]
gets called causing KeyError
, the secret_string
will be revealed to the SCIM client.
See https://datatracker.ietf.org/doc/html/rfc7643#section-4.1.1: (userName
)
This attribute is REQUIRED and is case insensitive.
Currently this case-insensitive behavior is not implemented and the filter
lookups are case-sensitive.
Describe the bug
PYPI describes the latest version as 0.16.4 but unable to install it.
To Reproduce
pip install django-scim2==0.16.4
Expected behavior
Stacktrace
Collecting django-scim2==0.16.4
Using cached django_scim2-0.16.3-py3-none-any.whl
ERROR: Requested django-scim2==0.16.4 from file:///Users/myuser/Library/Caches/pip/wheels/93/3f/bf/0a26b7ee52fda2c188c8d752d3d7611a6ff375c84b9d0deb01/django_scim2-0.16.3-py3-none-any.whl (from -r requirements.txt (line 64)) has different version in metadata: '0.16.3'
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.