def dispatch(self, request: HttpRequest, *_, **kwargs) -> HttpResponse: """View Get handler""" slug = kwargs.get("source_slug", "") try: self.source = OAuthSource.objects.get(slug=slug) except OAuthSource.DoesNotExist: raise Http404(f"Unknown OAuth source '{slug}'.") if not self.source.enabled: raise Http404(f"Source {slug} is not enabled.") client = self.get_client(self.source, callback=self.get_callback_url(self.source)) # Fetch access token token = client.get_access_token() if token is None: return self.handle_login_failure("Could not retrieve token.") if "error" in token: return self.handle_login_failure(token["error"]) # Fetch profile info try: raw_info = client.get_profile_info(token) if raw_info is None: return self.handle_login_failure("Could not retrieve profile.") except JSONDecodeError as exc: Event.new( EventAction.CONFIGURATION_ERROR, message="Failed to JSON-decode profile.", raw_profile=exc.doc, ).from_http(self.request) return self.handle_login_failure("Could not retrieve profile.") identifier = self.get_user_id(raw_info) if identifier is None: return self.handle_login_failure("Could not determine id.") # Get or create access record enroll_info = self.get_user_enroll_context(raw_info) sfm = OAuthSourceFlowManager( source=self.source, request=self.request, identifier=identifier, enroll_info=enroll_info, ) sfm.policy_context = {"oauth_userinfo": raw_info} return sfm.get_flow( access_token=token.get("access_token"), )
def validate_stage_templates(self): """Ensure all stage's templates actually exist""" from authentik.events.models import Event, EventAction from authentik.stages.email.models import EmailStage, EmailTemplates for stage in EmailStage.objects.all(): try: get_template(stage.template) except TemplateDoesNotExist: LOGGER.warning("Stage template does not exist, resetting", path=stage.template) Event.new( EventAction.CONFIGURATION_ERROR, stage=stage, message= (f"Template {stage.template} does not exist, resetting to default." f" (Stage {stage.name})"), ).save() stage.template = EmailTemplates.ACCOUNT_CONFIRM stage.save()
def get_redirect_url(self, parameters=None): "Build authentication redirect url." args = self.get_redirect_args() additional = parameters or {} args.update(additional) # Special handling for scope, since it's set as array # to make additional scopes easier args["scope"] = " ".join(sorted(set(args["scope"]))) params = urlencode(args, quote_via=quote) LOGGER.info("redirect args", **args) authorization_url = self.source.type.authorization_url or "" if self.source.type.urls_customizable and self.source.authorization_url: authorization_url = self.source.authorization_url if authorization_url == "": Event.new( EventAction.CONFIGURATION_ERROR, source=self.source, message="Source has an empty authorization URL.", ).save() return f"{authorization_url}?{params}"
def get_challenge(self, *args, **kwargs) -> Challenge: user = self.get_pending_user() stage: AuthenticatorDuoStage = self.executor.current_stage try: enroll = stage.client.enroll(user.username) except RuntimeError as exc: Event.new( EventAction.CONFIGURATION_ERROR, message=f"Failed to enroll user: {str(exc)}", user=user, ).from_http(self.request, user) raise InvalidStageError(str(exc)) from exc user_id = enroll["user_id"] self.request.session[SESSION_KEY_DUO_USER_ID] = user_id self.request.session[SESSION_KEY_DUO_ACTIVATION_CODE] = enroll[ "activation_code"] return AuthenticatorDuoChallenge( data={ "type": ChallengeTypes.NATIVE.value, "activation_barcode": enroll["activation_barcode"], "activation_code": enroll["activation_code"], "stage_uuid": str(stage.stage_uuid), })
def update_latest_version(self: MonitoredTask): """Update latest version info""" if CONFIG.y_bool("disable_update_check"): cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) self.set_status( TaskResult(TaskResultStatus.WARNING, messages=["Version check disabled."])) return try: response = get_http_session().get( "https://version.goauthentik.io/version.json", ) response.raise_for_status() data = response.json() upstream_version = data.get("stable", {}).get("version") cache.set(VERSION_CACHE_KEY, upstream_version, VERSION_CACHE_TIMEOUT) self.set_status( TaskResult(TaskResultStatus.SUCCESSFUL, ["Successfully updated latest Version"])) _set_prom_info() # Check if upstream version is newer than what we're running, # and if no event exists yet, create one. if LOCAL_VERSION < parse(upstream_version): # Event has already been created, don't create duplicate if Event.objects.filter( action=EventAction.UPDATE_AVAILABLE, context__new_version=upstream_version, ).exists(): return event_dict = {"new_version": upstream_version} if match := re.search(URL_FINDER, data.get("stable", {}).get("changelog", "")): event_dict["message"] = f"Changelog: {match.group()}" Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save() except (RequestException, IndexError) as exc: cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
def sync(self) -> int: """Iterate over all LDAP Groups and create authentik_core.Group instances""" if not self._source.sync_groups: self.message("Group syncing is disabled for this Source") return -1 groups = self._source.connection.extend.standard.paged_search( search_base=self.base_dn_groups, search_filter=self._source.group_object_filter, search_scope=ldap3.SUBTREE, attributes=[ ldap3.ALL_ATTRIBUTES, ldap3.ALL_OPERATIONAL_ATTRIBUTES ], ) group_count = 0 for group in groups: attributes = group.get("attributes", {}) group_dn = self._flatten( self._flatten(group.get("entryDN", group.get("dn")))) if self._source.object_uniqueness_field not in attributes: self.message( f"Cannot find uniqueness field in attributes: '{group_dn}'", attributes=attributes.keys(), dn=group_dn, ) continue uniq = self._flatten( attributes[self._source.object_uniqueness_field]) try: defaults = self.build_group_properties(group_dn, **attributes) self._logger.debug("Creating group with attributes", **defaults) if "name" not in defaults: raise IntegrityError( "Name was not set by propertymappings") # Special check for `users` field, as this is an M2M relation, and cannot be sync'd if "users" in defaults: del defaults["users"] ak_group, created = self.update_or_create_attributes( Group, { f"attributes__{LDAP_UNIQUENESS}": uniq, "parent": self._source.sync_parent_group, }, defaults, ) except (IntegrityError, FieldError, TypeError) as exc: Event.new( EventAction.CONFIGURATION_ERROR, message= (f"Failed to create group: {str(exc)} " "To merge new group with existing group, set the groups's " f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"), source=self._source, dn=group_dn, ).save() else: self._logger.debug("Synced group", group=ak_group.name, created=created) group_count += 1 return group_count
def view_key(self, request: Request, identifier: str) -> Response: """Return token key and log access""" token: Token = self.get_object() Event.new(EventAction.SECRET_VIEW, secret=token).from_http(request) # noqa # nosec return Response(TokenViewSerializer({"key": token.key}).data)