def on_user_logged_out(sender, request: HttpRequest, user: User, **_): """Delete temporary user if the `delete_on_logout` flag is enabled""" if not user: return if "saml" in user.attributes: if "delete_on_logout" in user.attributes["saml"]: if user.attributes["saml"]["delete_on_logout"]: LOGGER.debug("Deleted temporary user", user=user) user.delete()
def ms_check_uac(self, attributes: dict[str, Any], user: User): """Check userAccountControl""" if "userAccountControl" not in attributes: return # Default from https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity # /useraccountcontrol-manipulate-account-properties uac_bit = attributes.get("userAccountControl", 512) uac = UserAccountControl(uac_bit) user.is_active = UserAccountControl.ACCOUNTDISABLE not in uac user.save()
def test_oauth_enroll(self): """test OAuth Source With With OIDC""" self.create_objects() self.driver.get(self.live_server_url) flow_executor = self.get_shadow_root("ak-flow-executor") identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) wait = WebDriverWait(identification_stage, self.wait_timeout) wait.until( ec.presence_of_element_located( (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") ) ) identification_stage.find_element( By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" ).click() # Now we should be at the IDP, wait for the login field self.wait.until(ec.presence_of_element_located((By.NAME, "username"))) self.driver.find_element(By.NAME, "username").send_keys("example-user") self.driver.find_element(By.NAME, "username").send_keys(Keys.ENTER) sleep(2) # Wait until we're logged in self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "[name='confirm']"))) self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click() # Wait until we've loaded the user info page sleep(2) # Wait until we've logged in self.wait_for_url(self.if_user_url("/library")) self.driver.get(self.if_user_url("/settings")) self.assert_user(User(username="******", name="test name", email="*****@*****.**"))
def test_oauth_enroll_auth(self): """test OAuth Source With With OIDC (enroll and authenticate again)""" self.test_oauth_enroll() # We're logged in at the end of this, log out and re-login self.driver.get(self.url("authentik_flows:default-invalidation")) sleep(1) flow_executor = self.get_shadow_root("ak-flow-executor") identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) wait = WebDriverWait(identification_stage, self.wait_timeout) wait.until( ec.presence_of_element_located( (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") ) ) identification_stage.find_element( By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" ).click() # Now we should be at the IDP, wait for the login field self.wait.until(ec.presence_of_element_located((By.ID, "login"))) self.driver.find_element(By.ID, "login").send_keys("*****@*****.**") self.driver.find_element(By.ID, "password").send_keys("password") self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) # Wait until we're logged in self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "button[type=submit]"))) self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() # Wait until we've logged in self.wait_for_url(self.if_user_url("/library")) self.driver.get(self.if_user_url("/settings")) self.assert_user(User(username="******", name="admin", email="*****@*****.**"))
def ms_check_pwd_last_set(self, attributes: dict[str, Any], user: User, created: bool): """Check pwdLastSet""" if "pwdLastSet" not in attributes: return pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now()) pwd_last_set = pwd_last_set.replace(tzinfo=UTC) if created or pwd_last_set >= user.password_change_date: self.message(f"'{user.username}': Reset user's password") self._logger.debug( "Reset user's password", user=user.username, created=created, pwd_last_set=pwd_last_set, ) user.set_unusable_password() user.save()
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: """Validate that user exists, and optionally their password""" uid_field = attrs["uid_field"] current_stage: IdentificationStage = self.stage.executor.current_stage pre_user = self.stage.get_user(uid_field) if not pre_user: with Hub.current.start_span( op="authentik.stages.identification.validate_invalid_wait", description="Sleep random time on invalid user identifier", ): # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks sleep(0.030 * SystemRandom().randint(3, 7)) LOGGER.debug("invalid_login", identifier=uid_field) identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field) # We set the pending_user even on failure so it's part of the context, even # when the input is invalid # This is so its part of the current flow plan, and on flow restart can be kept, and # policies can be applied. self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User( username=uid_field, email=uid_field, ) if not current_stage.show_matched_user: self.stage.executor.plan.context[ PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = uid_field raise ValidationError("Failed to authenticate.") self.pre_user = pre_user if not current_stage.password_stage: # No password stage select, don't validate the password return attrs password = attrs.get("password", None) if not password: LOGGER.warning("Password not set for ident+auth attempt") try: with Hub.current.start_span( op="authentik.stages.identification.authenticate", description="User authenticate call (combo stage)", ): user = authenticate( self.stage.request, current_stage.password_stage.backends, username=self.pre_user.username, password=password, ) if not user: raise ValidationError("Failed to authenticate.") self.pre_user = user except PermissionDenied as exc: raise ValidationError(str(exc)) from exc return attrs
def test_oauth_enroll(self): """test OAuth Source With With OIDC""" self.create_objects() self.driver.get(self.live_server_url) flow_executor = self.get_shadow_root("ak-flow-executor") identification_stage = self.get_shadow_root( "ak-stage-identification", flow_executor ) wait = WebDriverWait(identification_stage, self.wait_timeout) wait.until( ec.presence_of_element_located( (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") ) ) identification_stage.find_element( By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" ).click() # Now we should be at the IDP, wait for the login field self.wait.until(ec.presence_of_element_located((By.ID, "login"))) self.driver.find_element(By.ID, "login").send_keys("*****@*****.**") self.driver.find_element(By.ID, "password").send_keys("password") self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) # Wait until we're logged in self.wait.until( ec.presence_of_element_located((By.CSS_SELECTOR, "button[type=submit]")) ) self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() # At this point we've been redirected back # and we're asked for the username flow_executor = self.get_shadow_root("ak-flow-executor") prompt_stage = self.get_shadow_root("ak-stage-prompt", flow_executor) prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").click() prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys( "foo" ) prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys( Keys.ENTER ) # Wait until we've logged in self.wait_for_url(self.if_admin_url("/library")) self.driver.get(self.if_admin_url("/user")) self.assert_user(User(username="******", name="admin", email="*****@*****.**"))
def get_pending_user(self) -> User: """Either show the matched User object or show what the user entered, based on what the earlier stage (mostly IdentificationStage) set. _USER_IDENTIFIER overrides the first User, as PENDING_USER is used for other things besides the form display. If no user is pending, returns request.user""" if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context: return User( username=self.executor.plan.context.get( PLAN_CONTEXT_PENDING_USER_IDENTIFIER), email="", ) if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context: return self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] return self.request.user
def authenticate( self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any ) -> Optional[User]: try: user = User._default_manager.get_by_natural_key(username) except User.DoesNotExist: # Run the default password hasher once to reduce the timing # difference between an existing and a nonexistent user (#20760). User().set_password(password) return None tokens = Token.filter_not_expired( user=user, key=password, intent=TokenIntents.INTENT_APP_PASSWORD ) if not tokens.exists(): return None token = tokens.first() self.set_method("token", request, token=token) return token.user
def get(self, request: HttpRequest) -> HttpResponse: """Save data in the current flow to the currently pending user. If no user is pending, a new user is created.""" if PLAN_CONTEXT_PROMPT not in self.executor.plan.context: message = _("No Pending data.") messages.error(request, message) LOGGER.debug(message) return self.executor.stage_invalid() data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] user_created = False if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User() self.executor.plan.context[ PLAN_CONTEXT_AUTHENTICATION_BACKEND ] = class_to_path(ModelBackend) LOGGER.debug( "Created new user", flow_slug=self.executor.flow.slug, ) user_created = True user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] # Before we change anything, check if the user is the same as in the request # and we're updating a password. In that case we need to update the session hash # Also check that we're not currently impersonating, so we don't update the session should_update_seesion = False if ( any("password" in x for x in data.keys()) and self.request.user.pk == user.pk and SESSION_IMPERSONATE_USER not in self.request.session ): should_update_seesion = True for key, value in data.items(): setter_name = f"set_{key}" # Check if user has a setter for this key, like set_password if hasattr(user, setter_name): setter = getattr(user, setter_name) if callable(setter): setter(value) # User has this key already elif hasattr(user, key): setattr(user, key, value) # Otherwise we just save it as custom attribute, but only if the value is prefixed with # `attribute_`, to prevent accidentally saving values else: if not key.startswith("attribute_"): LOGGER.debug("discarding key", key=key) continue user.attributes[key.replace("attribute_", "", 1)] = value # Extra check to prevent flows from saving a user with a blank username if user.username == "": LOGGER.warning("Aborting write to empty username", user=user) return self.executor.stage_invalid() # Check if we're writing from a source, and save the source to the attributes if PLAN_CONTEXT_SOURCES_CONNECTION in self.executor.plan.context: if USER_ATTRIBUTE_SOURCES not in user.attributes or not isinstance( user.attributes.get(USER_ATTRIBUTE_SOURCES), list ): user.attributes[USER_ATTRIBUTE_SOURCES] = [] connection: UserSourceConnection = self.executor.plan.context[ PLAN_CONTEXT_SOURCES_CONNECTION ] user.attributes[USER_ATTRIBUTE_SOURCES].append(connection.source.name) user.save() user_write.send( sender=self, request=request, user=user, data=data, created=user_created ) # Check if the password has been updated, and update the session auth hash if should_update_seesion: update_session_auth_hash(self.request, user) LOGGER.debug("Updated session hash", user=user) LOGGER.debug( "Updated existing user", user=user, flow_slug=self.executor.flow.slug, ) return self.executor.stage_ok()