def from_dict(self, d: Dict[str, Any]) -> None: """Consume a dictionary conforming to the SCIM User Schema. The dictionary was originally submitted as JSON by the client in PUT (update a user) and POST (create a new user) requests. A PUT request tells us to update User attributes to match those passed in the dict. A POST request tells us to create a new User with attributes as specified in the dict. The superclass implements some very basic default behavior, that doesn't support changing attributes via our actions.py functions (which update audit logs, send events, etc.) or doing application-specific validation. Thus, we've completely overridden the upstream implementation to store the values of the supported attributes that the request would like to change. Actually modifying the database is implemented in self.save(). Given that SCIMUser is an adapter class, this method is meant to be completely overridden, and we can expect it remain the case that no important django-scim2 logic relies on the superclass's implementation of this function. """ email = d.get("userName") assert isinstance(email, str) self.change_delivery_email(email) name_attr_dict = d.get("name", {}) if self.config["name_formatted_included"]: full_name = name_attr_dict.get("formatted", "") else: # Some providers (e.g. Okta) don't provide name.formatted. first_name = name_attr_dict.get("givenName", "") last_name = name_attr_dict.get("familyName", "") full_name = f"{first_name} {last_name}".strip() if full_name: assert isinstance(full_name, str) self.change_full_name(full_name) if self.is_new_user() and not full_name: raise scim_exceptions.BadRequestError( "Must specify name.formatted, name.givenName or name.familyName when creating a new user" ) active = d.get("active") if self.is_new_user() and not active: raise scim_exceptions.BadRequestError( "New user must have active=True") if active is not None: assert isinstance(active, bool) self.change_is_active(active)
def delete(self) -> None: """ This is consistent with Okta SCIM - users don't get DELETEd, they're deactivated by changing their "active" attr to False. """ raise scim_exceptions.BadRequestError( 'DELETE operation not supported. Use PUT or PATCH to modify the "active" attribute instead.' )
def from_dict(self, d): """ Consume a ``dict`` conforming to the SCIM User Schema, updating the internal user object with data from the ``dict``. Please note, the user object is not saved within this method. To persist the changes made by this method, please call ``.save()`` on the adapter. Eg:: scim_user.from_dict(d) scim_user.save() """ # Store dict for possible later use when saving user self._from_dict_copy = copy.deepcopy(d) self.obj.company_id = self.request.user.company_id self.parse_active(d.get('active')) self.obj.first_name = d.get('name', {}).get('givenName') or '' self.obj.last_name = d.get('name', {}).get('familyName') or '' self.parse_email(d.get('emails')) if self.is_new_user and not self.obj.email: raise scim_exceptions.BadRequestError('Empty email value') self.obj.scim_username = d.get('userName') self.obj.scim_external_id = d.get('externalId') or '' cleartext_password = d.get('password') if cleartext_password: self.obj.set_password(cleartext_password) self.obj._scim_cleartext_password = cleartext_password self.password_changed = True
def save(self) -> None: """ This method is called at the end of operations modifying a user, and is responsible for actually applying the requested changes, writing them to the database. """ realm = RequestNotes.get_notes(self._request).realm assert realm is not None email_new_value = getattr(self, "_email_new_value", None) is_active_new_value = getattr(self, "_is_active_new_value", None) full_name_new_value = getattr(self, "_full_name_new_value", None) password = getattr(self, "_password_set_to", None) # Clean up the internal "pending change" state, now that we've # fetched the values: self._email_new_value = None self._is_active_new_value = None self._full_name_new_value = None self._password_set_to = None if email_new_value: try: # Note that the validate_email check that usually # appears adjacent to email_allowed_for_realm is # present in save(). email_allowed_for_realm(email_new_value, realm) except DomainNotAllowedForRealmError: raise scim_exceptions.BadRequestError( "This email domain isn't allowed in this organization.") except DisposableEmailError: # nocoverage raise scim_exceptions.BadRequestError( "Disposable email domains are not allowed for this realm.") except EmailContainsPlusError: # nocoverage raise scim_exceptions.BadRequestError( "Email address can't contain + characters.") try: validate_email_not_already_in_realm(realm, email_new_value) except ValidationError as e: raise ConflictError("Email address already in use: " + str(e)) if self.is_new_user(): assert full_name_new_value is not None self.obj = do_create_user( email_new_value, password, realm, full_name_new_value, acting_user=None, ) return # TODO: The below operations should ideally be executed in a single # atomic block to avoid failing with partial changes getting saved. # This can be fixed once we figure out how do_deactivate_user can be run # inside an atomic block. # We process full_name first here, since it's the only one that can fail. if full_name_new_value: check_change_full_name(self.obj, full_name_new_value, acting_user=None) if email_new_value: do_change_user_delivery_email(self.obj, email_new_value) if is_active_new_value is not None and is_active_new_value: do_reactivate_user(self.obj, acting_user=None) elif is_active_new_value is not None and not is_active_new_value: do_deactivate_user(self.obj, acting_user=None)
def validate_email(email): try: validator = core.validators.EmailValidator() validator(email) except core.exceptions.ValidationError: raise scim_exceptions.BadRequestError('Invalid email value')