Example #1
0
    def __init__(self, provider: SAMLProvider, request: HttpRequest,
                 auth_n_request: AuthNRequest):
        self.provider = provider
        self.http_request = request
        self.auth_n_request = auth_n_request

        self._issue_instant = get_time_string()
        self._assertion_id = get_random_id()

        self._valid_not_before = get_time_string(
            timedelta_from_string(self.provider.assertion_valid_not_before))
        self._valid_not_on_or_after = get_time_string(
            timedelta_from_string(
                self.provider.assertion_valid_not_on_or_after))
Example #2
0
 def get(self, request: HttpRequest) -> HttpResponse:
     """Attach the currently pending user to the current session"""
     if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
         message = _("No Pending user to login.")
         messages.error(request, message)
         LOGGER.debug(message)
         return self.executor.stage_invalid()
     backend = self.executor.plan.context.get(
         PLAN_CONTEXT_AUTHENTICATION_BACKEND, DEFAULT_BACKEND
     )
     login(
         self.request,
         self.executor.plan.context[PLAN_CONTEXT_PENDING_USER],
         backend=backend,
     )
     delta = timedelta_from_string(self.executor.current_stage.session_duration)
     if delta.seconds == 0:
         self.request.session.set_expiry(0)
     else:
         self.request.session.set_expiry(delta)
     LOGGER.debug(
         "Logged in",
         user=self.executor.plan.context[PLAN_CONTEXT_PENDING_USER],
         flow_slug=self.executor.flow.slug,
         session_duration=self.executor.current_stage.session_duration,
     )
     messages.success(self.request, _("Successfully logged in!"))
     return self.executor.stage_ok()
Example #3
0
 def get(self, request: HttpRequest) -> HttpResponse:
     """Attach the currently pending user to the current session"""
     if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
         message = _("No Pending user to login.")
         messages.error(request, message)
         LOGGER.debug(message)
         return self.executor.stage_invalid()
     backend = self.executor.plan.context.get(
         PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT)
     user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
     if not user.is_active:
         LOGGER.warning("User is not active, login will not work.")
     login(
         self.request,
         user,
         backend=backend,
     )
     delta = timedelta_from_string(
         self.executor.current_stage.session_duration)
     if delta.total_seconds() == 0:
         self.request.session.set_expiry(0)
     else:
         self.request.session.set_expiry(delta)
     LOGGER.debug(
         "Logged in",
         backend=backend,
         user=user,
         flow_slug=self.executor.flow.slug,
         session_duration=self.executor.current_stage.session_duration,
     )
     self.request.session[USER_LOGIN_AUTHENTICATED] = True
     messages.success(self.request, _("Successfully logged in!"))
     return self.executor.stage_ok()
Example #4
0
def clean_temporary_users(self: MonitoredTask):
    """Remove temporary users created by SAML Sources"""
    _now = now()
    messages = []
    deleted_users = 0
    for user in User.objects.filter(attributes__saml__isnull=False):
        sources = SAMLSource.objects.filter(
            pk=user.attributes.get("saml", {}).get("source", ""))
        if not sources.exists():
            LOGGER.warning(
                "User has an invalid SAML Source and won't be deleted!",
                user=user)
            messages.append(
                f"User {user} has an invalid SAML Source and won't be deleted!"
            )
            continue
        source = sources.first()
        source_delta = timedelta_from_string(
            source.temporary_user_delete_after)
        if _now - user.last_login >= source_delta:
            LOGGER.debug("User is expired and will be deleted.",
                         user=user,
                         delta=source_delta)
            # TODO: Check if user is signed in anywhere?
            user.delete()
            deleted_users += 1
    messages.append(f"Successfully deleted {deleted_users} users.")
    self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
Example #5
0
 def test_event_retention(self):
     """Test tenant's event retention"""
     tenant = Tenant.objects.create(
         domain="foo",
         default=True,
         branding_title="custom",
         event_retention="weeks=3",
     )
     factory = RequestFactory()
     request = factory.get("/")
     request.tenant = tenant
     event = Event.new(action=EventAction.SYSTEM_EXCEPTION, message="test").from_http(request)
     self.assertEqual(event.expires.day, (event.created + timedelta_from_string("weeks=3")).day)
     self.assertEqual(
         event.expires.month,
         (event.created + timedelta_from_string("weeks=3")).month,
     )
     self.assertEqual(
         event.expires.year, (event.created + timedelta_from_string("weeks=3")).year
     )
Example #6
0
 def create_refresh_token(self, user: User, scope: list[str],
                          request: HttpRequest) -> "RefreshToken":
     """Create and populate a RefreshToken object."""
     token = RefreshToken(
         user=user,
         provider=self,
         refresh_token=uuid4().hex,
         expires=timezone.now() +
         timedelta_from_string(self.token_validity),
         scope=scope,
     )
     token.access_token = token.create_access_token(user, request)
     return token
Example #7
0
    def create_id_token(self, user: User, request: HttpRequest) -> IDToken:
        """Creates the id_token.
        See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken"""
        sub = ""
        if self.provider.sub_mode == SubModes.HASHED_USER_ID:
            sub = sha256(f"{user.id}-{settings.SECRET_KEY}".encode(
                "ascii")).hexdigest()
        elif self.provider.sub_mode == SubModes.USER_EMAIL:
            sub = user.email
        elif self.provider.sub_mode == SubModes.USER_USERNAME:
            sub = user.username
        elif self.provider.sub_mode == SubModes.USER_UPN:
            sub = user.attributes["upn"]
        else:
            raise ValueError((f"Provider {self.provider} has invalid sub_mode "
                              f"selected: {self.provider.sub_mode}"))

        # Convert datetimes into timestamps.
        now = int(time.time())
        iat_time = now
        exp_time = int(
            now + timedelta_from_string(self.provider.token_validity).seconds)
        # We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
        auth_events = Event.objects.filter(
            action=EventAction.LOGIN, user=get_user(user)).order_by("-created")
        # Fallback in case we can't find any login events
        auth_time = datetime.now()
        if auth_events.exists():
            auth_time = auth_events.first().created
        auth_time = int(dateformat.format(auth_time, "U"))

        token = IDToken(
            iss=self.provider.get_issuer(request),
            sub=sub,
            aud=self.provider.client_id,
            exp=exp_time,
            iat=iat_time,
            auth_time=auth_time,
        )

        # Include (or not) user standard claims in the id_token.
        if self.provider.include_claims_in_id_token:
            from authentik.providers.oauth2.views.userinfo import UserInfoView

            user_info = UserInfoView()
            user_info.request = request
            claims = user_info.get_claims(self)
            token.claims = claims

        return token
Example #8
0
    def create_refresh_response(self) -> dict[str, Any]:
        """See https://tools.ietf.org/html/rfc6749#section-6"""

        unauthorized_scopes = set(self.params.scope) - set(
            self.params.refresh_token.scope)
        if unauthorized_scopes:
            raise TokenError("invalid_scope")

        provider: OAuth2Provider = self.params.refresh_token.provider

        refresh_token: RefreshToken = provider.create_refresh_token(
            user=self.params.refresh_token.user,
            scope=self.params.scope,
            request=self.request,
        )

        # If the Token has an id_token it's an Authentication request.
        if self.params.refresh_token.id_token:
            refresh_token.id_token = refresh_token.create_id_token(
                user=self.params.refresh_token.user,
                request=self.request,
            )
            refresh_token.id_token.at_hash = refresh_token.at_hash

            # Store the refresh_token.
            refresh_token.save()

        # Mark old token as revoked
        self.params.refresh_token.revoked = True
        self.params.refresh_token.save()

        return {
            "access_token":
            refresh_token.access_token,
            "refresh_token":
            refresh_token.refresh_token,
            "token_type":
            "bearer",
            "expires_in":
            int(
                timedelta_from_string(
                    refresh_token.provider.token_validity).total_seconds()),
            "id_token":
            self.params.provider.encode(refresh_token.id_token.to_dict()),
        }
Example #9
0
 def from_http(self,
               request: HttpRequest,
               user: Optional[settings.AUTH_USER_MODEL] = None) -> "Event":
     """Add data from a Django-HttpRequest, allowing the creation of
     Events independently from requests.
     `user` arguments optionally overrides user from requests."""
     if request:
         self.context["http_request"] = {
             "path": request.path,
             "method": request.method,
             "args": QueryDict(request.META.get("QUERY_STRING", "")),
         }
     if hasattr(request, "tenant"):
         tenant: Tenant = request.tenant
         # Because self.created only gets set on save, we can't use it's value here
         # hence we set self.created to now and then use it
         self.created = now()
         self.expires = self.created + timedelta_from_string(
             tenant.event_retention)
         self.tenant = sanitize_dict(model_to_dict(tenant))
     if hasattr(request, "user"):
         original_user = None
         if hasattr(request, "session"):
             original_user = request.session.get(
                 SESSION_IMPERSONATE_ORIGINAL_USER, None)
         self.user = get_user(request.user, original_user)
     if user:
         self.user = get_user(user)
     # Check if we're currently impersonating, and add that user
     if hasattr(request, "session"):
         if SESSION_IMPERSONATE_ORIGINAL_USER in request.session:
             self.user = get_user(
                 request.session[SESSION_IMPERSONATE_ORIGINAL_USER])
             self.user["on_behalf_of"] = get_user(
                 request.session[SESSION_IMPERSONATE_USER])
     # User 255.255.255.255 as fallback if IP cannot be determined
     self.client_ip = get_client_ip(request)
     # Apply GeoIP Data, when enabled
     self.with_geoip()
     # If there's no app set, we get it from the requests too
     if not self.app:
         self.app = Event._get_app_from_request(request)
     self.save()
     return self
Example #10
0
    def create_code(self, request: HttpRequest) -> AuthorizationCode:
        """Create an AuthorizationCode object for the request"""
        code = AuthorizationCode()
        code.user = request.user
        code.provider = self.provider

        code.code = uuid4().hex

        if self.code_challenge and self.code_challenge_method:
            code.code_challenge = self.code_challenge
            code.code_challenge_method = self.code_challenge_method

        code.expires_at = timezone.now() + timedelta_from_string(
            self.provider.access_code_validity)
        code.scope = self.scope
        code.nonce = self.nonce
        code.is_open_id = SCOPE_OPENID in self.scope

        return code
Example #11
0
 def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
     current_stage: ConsentStage = self.executor.current_stage
     if PLAN_CONTEXT_APPLICATION not in self.executor.plan.context:
         return self.executor.stage_ok()
     application = self.executor.plan.context[PLAN_CONTEXT_APPLICATION]
     # Make this StageView work when injected, in which case `current_stage` is an instance
     # of the base class, and we don't save any consent, as it is assumed to be a one-time
     # prompt
     if not isinstance(current_stage, ConsentStage):
         return self.executor.stage_ok()
     # Since we only get here when no consent exists, we can create it without update
     if current_stage.mode == ConsentMode.PERMANENT:
         UserConsent.objects.create(
             user=self.request.user, application=application, expiring=False
         )
     if current_stage.mode == ConsentMode.EXPIRING:
         UserConsent.objects.create(
             user=self.request.user,
             application=application,
             expires=now() + timedelta_from_string(current_stage.consent_expire_in),
         )
     return self.executor.stage_ok()
Example #12
0
    def create_code_response(self) -> dict[str, Any]:
        """See https://tools.ietf.org/html/rfc6749#section-4.1"""

        refresh_token = self.params.authorization_code.provider.create_refresh_token(
            user=self.params.authorization_code.user,
            scope=self.params.authorization_code.scope,
            request=self.request,
        )

        if self.params.authorization_code.is_open_id:
            id_token = refresh_token.create_id_token(
                user=self.params.authorization_code.user,
                request=self.request,
            )
            id_token.nonce = self.params.authorization_code.nonce
            id_token.at_hash = refresh_token.at_hash
            refresh_token.id_token = id_token

        # Store the token.
        refresh_token.save()

        # We don't need to store the code anymore.
        self.params.authorization_code.delete()

        return {
            "access_token":
            refresh_token.access_token,
            "refresh_token":
            refresh_token.refresh_token,
            "token_type":
            "bearer",
            "expires_in":
            int(
                timedelta_from_string(
                    self.params.provider.token_validity).total_seconds()),
            "id_token":
            refresh_token.provider.encode(refresh_token.id_token.to_dict()),
        }
Example #13
0
    def create_implicit_response(self, code: Optional[AuthorizationCode]) -> dict:
        """Create implicit response's URL Fragment dictionary"""
        query_fragment = {}

        token = self.provider.create_refresh_token(
            user=self.request.user,
            scope=self.params.scope,
            request=self.request,
        )

        # Check if response_type must include access_token in the response.
        if self.params.response_type in [
            ResponseTypes.ID_TOKEN_TOKEN,
            ResponseTypes.CODE_ID_TOKEN_TOKEN,
            ResponseTypes.ID_TOKEN,
            ResponseTypes.CODE_TOKEN,
        ]:
            query_fragment["access_token"] = token.access_token

        # We don't need id_token if it's an OAuth2 request.
        if SCOPE_OPENID in self.params.scope:
            id_token = token.create_id_token(
                user=self.request.user,
                request=self.request,
            )
            id_token.nonce = self.params.nonce

            # Include at_hash when access_token is being returned.
            if "access_token" in query_fragment:
                id_token.at_hash = token.at_hash

            if self.params.response_type in [
                ResponseTypes.CODE_ID_TOKEN,
                ResponseTypes.CODE_ID_TOKEN_TOKEN,
            ]:
                id_token.c_hash = code.c_hash

            # Check if response_type must include id_token in the response.
            if self.params.response_type in [
                ResponseTypes.ID_TOKEN,
                ResponseTypes.ID_TOKEN_TOKEN,
                ResponseTypes.CODE_ID_TOKEN,
                ResponseTypes.CODE_ID_TOKEN_TOKEN,
            ]:
                query_fragment["id_token"] = self.provider.encode(id_token.to_dict())
            token.id_token = id_token

        # Store the token.
        token.save()

        # Code parameter must be present if it's Hybrid Flow.
        if self.params.grant_type == GrantTypes.HYBRID:
            query_fragment["code"] = code.code

        query_fragment["token_type"] = "bearer"
        query_fragment["expires_in"] = timedelta_from_string(
            self.provider.token_validity
        ).seconds
        query_fragment["state"] = self.params.state if self.params.state else ""

        return query_fragment
Example #14
0
 def get_token_validity(self, obj: ProxyProvider) -> Optional[float]:
     """Get token validity as second count"""
     return timedelta_from_string(obj.token_validity).total_seconds()
Example #15
0
 def test_invalid(self):
     """Test invalid expression"""
     with self.assertRaises(ValueError):
         timedelta_from_string("foo")
     with self.assertRaises(ValueError):
         timedelta_from_string("bar=baz")
Example #16
0
 def test_valid(self):
     """Test valid expression"""
     expr = "hours=3;minutes=1"
     expected = timedelta(hours=3, minutes=1)
     self.assertEqual(timedelta_from_string(expr), expected)