2022-01-11 18:49:06 +00:00
|
|
|
import datetime
|
2023-08-23 13:18:43 +00:00
|
|
|
from typing import List
|
|
|
|
from typing import Optional
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
from authlib.oauth2.rfc6749 import AuthorizationCodeMixin
|
|
|
|
from authlib.oauth2.rfc6749 import ClientMixin
|
|
|
|
from authlib.oauth2.rfc6749 import TokenMixin
|
|
|
|
from authlib.oauth2.rfc6749 import util
|
2023-04-09 09:37:04 +00:00
|
|
|
from canaille.app import models
|
2023-08-23 13:18:43 +00:00
|
|
|
from canaille.core.models import User
|
2023-04-10 11:45:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Client(ClientMixin):
|
2023-08-17 13:55:41 +00:00
|
|
|
"""
|
|
|
|
OpenID Connect client definition.
|
|
|
|
"""
|
|
|
|
|
2023-08-23 13:18:43 +00:00
|
|
|
id: str
|
|
|
|
description: Optional[str]
|
|
|
|
preconsent: Optional[bool]
|
|
|
|
post_logout_redirect_uris: List[str]
|
|
|
|
audience: List["Client"]
|
|
|
|
client_id: Optional[str]
|
|
|
|
client_secret: Optional[str]
|
|
|
|
client_id_issued_at: Optional[datetime.datetime]
|
|
|
|
client_secret_expires_at: Optional[datetime.datetime]
|
|
|
|
client_name: Optional[str]
|
|
|
|
contacts: List[str]
|
|
|
|
client_uri: Optional[str]
|
|
|
|
redirect_uris: List[str]
|
|
|
|
logo_uri: Optional[str]
|
|
|
|
grant_types: List[str]
|
|
|
|
response_types: List[str]
|
|
|
|
scope: Optional[str]
|
|
|
|
tos_uri: Optional[str]
|
|
|
|
policy_uri: Optional[str]
|
|
|
|
jwks_uri: Optional[str]
|
|
|
|
jwk: Optional[str]
|
|
|
|
token_endpoint_auth_method: Optional[str]
|
|
|
|
software_id: Optional[str]
|
|
|
|
software_version: Optional[str]
|
|
|
|
|
2023-04-10 11:45:52 +00:00
|
|
|
client_info_attributes = [
|
|
|
|
"client_id",
|
|
|
|
"client_secret",
|
|
|
|
"client_id_issued_at",
|
|
|
|
"client_secret_expires_at",
|
|
|
|
]
|
|
|
|
|
|
|
|
client_metadata_attributes = [
|
|
|
|
"client_name",
|
|
|
|
"contacts",
|
|
|
|
"client_uri",
|
|
|
|
"redirect_uris",
|
|
|
|
"logo_uri",
|
|
|
|
"grant_types",
|
|
|
|
"response_types",
|
|
|
|
"scope",
|
|
|
|
"tos_uri",
|
|
|
|
"policy_uri",
|
|
|
|
"jwks_uri",
|
|
|
|
"jwk",
|
|
|
|
"token_endpoint_auth_method",
|
|
|
|
"software_id",
|
|
|
|
"software_version",
|
|
|
|
]
|
2022-01-11 18:49:06 +00:00
|
|
|
|
2022-10-24 15:18:46 +00:00
|
|
|
def get_client_id(self):
|
|
|
|
return self.client_id
|
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
def get_default_redirect_uri(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
return self.redirect_uris[0]
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_allowed_scope(self, scope):
|
2022-07-07 14:05:34 +00:00
|
|
|
return util.list_to_scope(
|
|
|
|
[scope_piece for scope_piece in self.scope if scope_piece in scope]
|
|
|
|
)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def check_redirect_uri(self, redirect_uri):
|
2022-01-11 16:57:58 +00:00
|
|
|
return redirect_uri in self.redirect_uris
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def check_client_secret(self, client_secret):
|
2022-10-17 15:49:52 +00:00
|
|
|
return client_secret == self.client_secret
|
2022-01-11 18:49:06 +00:00
|
|
|
|
2022-04-10 14:00:51 +00:00
|
|
|
def check_endpoint_auth_method(self, method, endpoint):
|
|
|
|
if endpoint == "token":
|
|
|
|
return method == self.token_endpoint_auth_method
|
|
|
|
return True
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def check_response_type(self, response_type):
|
2022-10-17 15:49:52 +00:00
|
|
|
return all(r in self.response_types for r in response_type.split(" "))
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def check_grant_type(self, grant_type):
|
2022-10-17 15:49:52 +00:00
|
|
|
return grant_type in self.grant_types
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def client_info(self):
|
2022-10-24 15:18:46 +00:00
|
|
|
result = {
|
|
|
|
attribute_name: getattr(self, attribute_name)
|
|
|
|
for attribute_name in self.client_info_attributes
|
|
|
|
}
|
|
|
|
result["client_id_issued_at"] = int(
|
|
|
|
datetime.datetime.timestamp(result["client_id_issued_at"])
|
2022-01-11 18:49:06 +00:00
|
|
|
)
|
2022-10-24 15:18:46 +00:00
|
|
|
return result
|
|
|
|
|
|
|
|
@property
|
|
|
|
def client_metadata(self):
|
2023-01-28 13:04:04 +00:00
|
|
|
metadata = {
|
2022-10-24 15:18:46 +00:00
|
|
|
attribute_name: getattr(self, attribute_name)
|
|
|
|
for attribute_name in self.client_metadata_attributes
|
|
|
|
}
|
2023-01-28 17:35:39 +00:00
|
|
|
metadata["scope"] = " ".join(metadata["scope"])
|
2023-01-28 13:04:04 +00:00
|
|
|
return metadata
|
2022-01-11 18:49:06 +00:00
|
|
|
|
2023-01-30 18:58:00 +00:00
|
|
|
def delete(self):
|
2023-04-09 09:37:04 +00:00
|
|
|
for consent in models.Consent.query(client=self):
|
2023-01-30 18:58:00 +00:00
|
|
|
consent.delete()
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
for code in models.AuthorizationCode.query(client=self):
|
2023-01-30 18:58:00 +00:00
|
|
|
code.delete()
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
for token in models.Token.query(client=self):
|
2023-01-30 18:58:00 +00:00
|
|
|
token.delete()
|
|
|
|
|
|
|
|
super().delete()
|
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
|
2023-04-10 11:45:52 +00:00
|
|
|
class AuthorizationCode(AuthorizationCodeMixin):
|
2023-08-17 13:55:41 +00:00
|
|
|
"""
|
|
|
|
OpenID Connect temporary authorization code definition.
|
|
|
|
"""
|
|
|
|
|
2023-08-23 13:18:43 +00:00
|
|
|
id: str
|
|
|
|
authorization_code_id: str
|
|
|
|
code: str
|
|
|
|
client: "Client"
|
|
|
|
subject: User
|
|
|
|
redirect_uri: Optional[str]
|
|
|
|
response_type: Optional[str]
|
|
|
|
scope: Optional[str]
|
|
|
|
nonce: Optional[str]
|
|
|
|
issue_date: datetime.datetime
|
|
|
|
lifetime: int
|
|
|
|
challenge: Optional[str]
|
|
|
|
challenge_method: Optional[str]
|
|
|
|
revokation_date: datetime.datetime
|
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
def get_redirect_uri(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
return self.redirect_uri
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_scope(self):
|
2022-07-07 14:05:34 +00:00
|
|
|
return self.scope[0].split(" ")
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_nonce(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
return self.nonce
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def is_expired(self):
|
2023-03-17 23:38:56 +00:00
|
|
|
return self.issue_date + datetime.timedelta(
|
|
|
|
seconds=int(self.lifetime)
|
|
|
|
) < datetime.datetime.now(datetime.timezone.utc)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_auth_time(self):
|
2023-03-17 23:38:56 +00:00
|
|
|
return int(
|
|
|
|
(
|
|
|
|
self.issue_date
|
|
|
|
- datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
|
|
).total_seconds()
|
|
|
|
)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
|
2023-04-10 11:45:52 +00:00
|
|
|
class Token(TokenMixin):
|
2023-08-17 13:55:41 +00:00
|
|
|
"""
|
|
|
|
OpenID Connect token definition.
|
|
|
|
"""
|
|
|
|
|
2023-08-23 13:18:43 +00:00
|
|
|
id: str
|
|
|
|
token_id: str
|
|
|
|
access_token: str
|
|
|
|
client: "Client"
|
|
|
|
subject: User
|
|
|
|
type: str
|
|
|
|
refresh_token: str
|
|
|
|
scope: str
|
|
|
|
issue_date: datetime.datetime
|
|
|
|
lifetime: int
|
|
|
|
revokation_date: datetime.datetime
|
|
|
|
audience: List["Client"]
|
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
@property
|
|
|
|
def expire_date(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
return self.issue_date + datetime.timedelta(seconds=int(self.lifetime))
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def revoked(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
return bool(self.revokation_date)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_scope(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
return " ".join(self.scope)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_issued_at(self):
|
2023-03-17 23:38:56 +00:00
|
|
|
return int(
|
|
|
|
(
|
|
|
|
self.issue_date
|
|
|
|
- datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
|
|
).total_seconds()
|
|
|
|
)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def get_expires_at(self):
|
|
|
|
issue_timestamp = (
|
2023-03-17 23:38:56 +00:00
|
|
|
self.issue_date
|
|
|
|
- datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
2022-01-11 18:49:06 +00:00
|
|
|
).total_seconds()
|
2022-01-11 16:57:58 +00:00
|
|
|
return int(issue_timestamp) + int(self.lifetime)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def is_refresh_token_active(self):
|
2022-01-11 16:57:58 +00:00
|
|
|
if self.revokation_date:
|
2022-01-11 18:49:06 +00:00
|
|
|
return False
|
|
|
|
|
2023-03-17 23:38:56 +00:00
|
|
|
return self.expire_date >= datetime.datetime.now(datetime.timezone.utc)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
|
|
|
def is_expired(self):
|
2023-03-17 23:38:56 +00:00
|
|
|
return self.issue_date + datetime.timedelta(
|
|
|
|
seconds=int(self.lifetime)
|
|
|
|
) < datetime.datetime.now(datetime.timezone.utc)
|
2022-01-11 18:49:06 +00:00
|
|
|
|
2022-04-10 14:00:51 +00:00
|
|
|
def is_revoked(self):
|
|
|
|
return bool(self.revokation_date)
|
|
|
|
|
|
|
|
def check_client(self, client):
|
2023-03-08 22:53:53 +00:00
|
|
|
return client.client_id == self.client.client_id
|
2022-04-10 14:00:51 +00:00
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
|
2023-04-10 11:45:52 +00:00
|
|
|
class Consent:
|
2023-08-17 13:55:41 +00:00
|
|
|
"""
|
|
|
|
Long-term user consent to an application.
|
|
|
|
"""
|
|
|
|
|
2023-08-23 13:18:43 +00:00
|
|
|
id: str
|
|
|
|
consent_id: str
|
|
|
|
subject: User
|
|
|
|
client: "Client"
|
|
|
|
scope: str
|
|
|
|
issue_date: datetime.datetime
|
|
|
|
revokation_date: datetime.datetime
|
|
|
|
|
2023-02-14 17:43:43 +00:00
|
|
|
@property
|
|
|
|
def revoked(self):
|
|
|
|
return bool(self.revokation_date)
|
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
def revoke(self):
|
2023-03-17 23:38:56 +00:00
|
|
|
self.revokation_date = datetime.datetime.now(datetime.timezone.utc)
|
2022-01-11 18:49:06 +00:00
|
|
|
self.save()
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
tokens = models.Token.query(
|
2023-05-17 10:07:52 +00:00
|
|
|
client=self.client,
|
|
|
|
subject=self.subject,
|
2022-01-11 18:49:06 +00:00
|
|
|
)
|
2023-02-14 17:43:43 +00:00
|
|
|
tokens = [token for token in tokens if not token.revoked]
|
2022-01-11 18:49:06 +00:00
|
|
|
for t in tokens:
|
2022-01-11 16:57:58 +00:00
|
|
|
t.revokation_date = self.revokation_date
|
2022-01-11 18:49:06 +00:00
|
|
|
t.save()
|
2023-02-14 17:43:43 +00:00
|
|
|
|
|
|
|
def restore(self):
|
|
|
|
self.revokation_date = None
|
|
|
|
self.save()
|