forked from Github-Mirrors/canaille
LDAPObject dn attributes are automatically initialized
This commit is contained in:
parent
d201d6f617
commit
53581404ab
26 changed files with 238 additions and 195 deletions
|
@ -32,7 +32,7 @@ def create_group(user):
|
||||||
flash(_("Group creation failed."), "error")
|
flash(_("Group creation failed."), "error")
|
||||||
else:
|
else:
|
||||||
group = Group()
|
group = Group()
|
||||||
group.member = [user.dn]
|
group.member = [user]
|
||||||
group.cn = [form.name.data]
|
group.cn = [form.name.data]
|
||||||
group.description = [form.description.data]
|
group.description = [form.description.data]
|
||||||
group.save()
|
group.save()
|
||||||
|
|
|
@ -155,7 +155,7 @@ def validate_configuration(config):
|
||||||
|
|
||||||
group = Group(
|
group = Group(
|
||||||
cn=f"canaille_{uuid.uuid4()}",
|
cn=f"canaille_{uuid.uuid4()}",
|
||||||
member=[user.dn],
|
member=[user],
|
||||||
)
|
)
|
||||||
group.save(conn)
|
group.save(conn)
|
||||||
group.delete(conn)
|
group.delete(conn)
|
||||||
|
|
|
@ -6,7 +6,24 @@ from .utils import ldap_to_python
|
||||||
from .utils import python_to_ldap
|
from .utils import python_to_ldap
|
||||||
|
|
||||||
|
|
||||||
class LDAPObject:
|
class LDAPObjectMetaclass(type):
|
||||||
|
ldap_to_python_class = {}
|
||||||
|
|
||||||
|
def __new__(cls, name, bases, attrs):
|
||||||
|
klass = super().__new__(cls, name, bases, attrs)
|
||||||
|
if attrs.get("object_class"):
|
||||||
|
for oc in attrs["object_class"]:
|
||||||
|
cls.ldap_to_python_class[oc] = klass
|
||||||
|
return klass
|
||||||
|
|
||||||
|
def __setattr__(cls, name, value):
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
if name == "object_class":
|
||||||
|
for oc in value:
|
||||||
|
cls.ldap_to_python_class[oc] = cls
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPObject(metaclass=LDAPObjectMetaclass):
|
||||||
_object_class_by_name = None
|
_object_class_by_name = None
|
||||||
_attribute_type_by_name = None
|
_attribute_type_by_name = None
|
||||||
_may = None
|
_may = None
|
||||||
|
@ -22,23 +39,44 @@ class LDAPObject:
|
||||||
self.changes = {}
|
self.changes = {}
|
||||||
|
|
||||||
kwargs.setdefault("objectClass", self.object_class)
|
kwargs.setdefault("objectClass", self.object_class)
|
||||||
|
|
||||||
for name, value in kwargs.items():
|
for name, value in kwargs.items():
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} {self.rdn_attribute}={self.rdn_value}>"
|
return (
|
||||||
|
f"<{self.__class__.__name__} {self.rdn_attribute}={self.rdn_value}>"
|
||||||
|
if self.rdn_attribute
|
||||||
|
else "<LDAPOBject>"
|
||||||
|
)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (
|
if not (
|
||||||
isinstance(other, self.__class__)
|
isinstance(other, self.__class__)
|
||||||
and self.may() == other.may()
|
and self.may() == other.may()
|
||||||
and self.must() == other.must()
|
and self.must() == other.must()
|
||||||
and all(
|
and all(
|
||||||
getattr(self, attr) == getattr(other, attr)
|
hasattr(self, attr) == hasattr(other, attr)
|
||||||
for attr in self.may() + self.must()
|
for attr in self.may() + self.must()
|
||||||
if hasattr(self, attr) and hasattr(other, attr)
|
|
||||||
)
|
)
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self_attributes = self.python_attrs_to_ldap(
|
||||||
|
{
|
||||||
|
attr: getattr(self, attr)
|
||||||
|
for attr in self.may() + self.must()
|
||||||
|
if hasattr(self, attr)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
other_attributes = other.python_attrs_to_ldap(
|
||||||
|
{
|
||||||
|
attr: getattr(other, attr)
|
||||||
|
for attr in self.may() + self.must()
|
||||||
|
if hasattr(self, attr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return self_attributes == other_attributes
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.dn)
|
return hash(self.dn)
|
||||||
|
@ -59,8 +97,7 @@ class LDAPObject:
|
||||||
|
|
||||||
# Lazy conversion from ldap format to python format
|
# Lazy conversion from ldap format to python format
|
||||||
if any(isinstance(value, bytes) for value in self.attrs[name]):
|
if any(isinstance(value, bytes) for value in self.attrs[name]):
|
||||||
ldap_attrs = LDAPObject.ldap_object_attributes()
|
syntax = self.attribute_ldap_syntax(name)
|
||||||
syntax = ldap_attrs[name].syntax if name in ldap_attrs else None
|
|
||||||
self.attrs[name] = [
|
self.attrs[name] = [
|
||||||
ldap_to_python(value, syntax) for value in self.attrs[name]
|
ldap_to_python(value, syntax) for value in self.attrs[name]
|
||||||
]
|
]
|
||||||
|
@ -102,8 +139,6 @@ class LDAPObject:
|
||||||
return self._may
|
return self._may
|
||||||
|
|
||||||
def must(self):
|
def must(self):
|
||||||
if not self._must:
|
|
||||||
self.update_ldap_attributes()
|
|
||||||
return self._must
|
return self._must
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -178,7 +213,6 @@ class LDAPObject:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def python_attrs_to_ldap(cls, attrs, encode=True):
|
def python_attrs_to_ldap(cls, attrs, encode=True):
|
||||||
ldap_attrs = LDAPObject.ldap_object_attributes()
|
|
||||||
if cls.attribute_table:
|
if cls.attribute_table:
|
||||||
attrs = {
|
attrs = {
|
||||||
cls.attribute_table.get(name, name): values
|
cls.attribute_table.get(name, name): values
|
||||||
|
@ -186,16 +220,23 @@ class LDAPObject:
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name: [
|
name: [
|
||||||
python_to_ldap(
|
python_to_ldap(value, cls.attribute_ldap_syntax(name), encode=encode)
|
||||||
value,
|
|
||||||
ldap_attrs[name].syntax if name in ldap_attrs else None,
|
|
||||||
encode=encode,
|
|
||||||
)
|
|
||||||
for value in (values if isinstance(values, list) else [values])
|
for value in (values if isinstance(values, list) else [values])
|
||||||
]
|
]
|
||||||
for name, values in attrs.items()
|
for name, values in attrs.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def attribute_ldap_syntax(cls, attribute_name):
|
||||||
|
ldap_attrs = LDAPObject.ldap_object_attributes()
|
||||||
|
if attribute_name not in ldap_attrs:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if ldap_attrs[attribute_name].syntax:
|
||||||
|
return ldap_attrs[attribute_name].syntax
|
||||||
|
|
||||||
|
return cls.attribute_ldap_syntax(ldap_attrs[attribute_name].sup[0])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, dn=None, filter=None, conn=None, **kwargs):
|
def get(cls, dn=None, filter=None, conn=None, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
@ -241,11 +282,20 @@ class LDAPObject:
|
||||||
|
|
||||||
objects = []
|
objects = []
|
||||||
for _, args in result:
|
for _, args in result:
|
||||||
|
cls = cls.guess_class(args["objectClass"])
|
||||||
obj = cls()
|
obj = cls()
|
||||||
obj.attrs = args
|
obj.attrs = args
|
||||||
objects.append(obj)
|
objects.append(obj)
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def guess_class(cls, object_classes):
|
||||||
|
if cls == LDAPObject:
|
||||||
|
for oc in object_classes:
|
||||||
|
if oc.decode() in LDAPObjectMetaclass.ldap_to_python_class:
|
||||||
|
return LDAPObjectMetaclass.ldap_to_python_class[oc.decode()]
|
||||||
|
return cls
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_ldap_attributes(cls):
|
def update_ldap_attributes(cls):
|
||||||
all_object_classes = cls.ldap_object_classes()
|
all_object_classes = cls.ldap_object_classes()
|
||||||
|
|
|
@ -6,7 +6,9 @@ LDAP_NULL_DATE = "000001010000Z"
|
||||||
|
|
||||||
class Syntax(str, Enum):
|
class Syntax(str, Enum):
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
BINARY = "1.3.6.1.4.1.1466.115.121.1.5"
|
||||||
BOOLEAN = "1.3.6.1.4.1.1466.115.121.1.7"
|
BOOLEAN = "1.3.6.1.4.1.1466.115.121.1.7"
|
||||||
|
DISTINGUISHED_NAME = "1.3.6.1.4.1.1466.115.121.1.12"
|
||||||
DIRECTORY_STRING = "1.3.6.1.4.1.1466.115.121.1.15"
|
DIRECTORY_STRING = "1.3.6.1.4.1.1466.115.121.1.15"
|
||||||
FAX_IMAGE = "1.3.6.1.4.1.1466.115.121.1.23"
|
FAX_IMAGE = "1.3.6.1.4.1.1466.115.121.1.23"
|
||||||
GENERALIZED_TIME = "1.3.6.1.4.1.1466.115.121.1.24"
|
GENERALIZED_TIME = "1.3.6.1.4.1.1466.115.121.1.24"
|
||||||
|
@ -22,6 +24,8 @@ class Syntax(str, Enum):
|
||||||
|
|
||||||
|
|
||||||
def ldap_to_python(value, syntax):
|
def ldap_to_python(value, syntax):
|
||||||
|
from .ldapobject import LDAPObject
|
||||||
|
|
||||||
if syntax == Syntax.GENERALIZED_TIME:
|
if syntax == Syntax.GENERALIZED_TIME:
|
||||||
value = value.decode("utf-8")
|
value = value.decode("utf-8")
|
||||||
if value == LDAP_NULL_DATE:
|
if value == LDAP_NULL_DATE:
|
||||||
|
@ -39,6 +43,9 @@ def ldap_to_python(value, syntax):
|
||||||
if syntax == Syntax.BOOLEAN:
|
if syntax == Syntax.BOOLEAN:
|
||||||
return value.decode("utf-8").upper() == "TRUE"
|
return value.decode("utf-8").upper() == "TRUE"
|
||||||
|
|
||||||
|
if syntax == Syntax.DISTINGUISHED_NAME:
|
||||||
|
return LDAPObject.get(dn=value.decode("utf-8"))
|
||||||
|
|
||||||
return value.decode("utf-8")
|
return value.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,6 +66,9 @@ def python_to_ldap(value, syntax, encode=True):
|
||||||
if syntax == Syntax.BOOLEAN and isinstance(value, bool):
|
if syntax == Syntax.BOOLEAN and isinstance(value, bool):
|
||||||
value = "TRUE" if value else "FALSE"
|
value = "TRUE" if value else "FALSE"
|
||||||
|
|
||||||
|
if syntax == Syntax.DISTINGUISHED_NAME:
|
||||||
|
value = value.dn
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -190,16 +190,12 @@ class Group(LDAPObject):
|
||||||
return self[attribute][0]
|
return self[attribute][0]
|
||||||
|
|
||||||
def get_members(self, conn=None):
|
def get_members(self, conn=None):
|
||||||
return [
|
return [member for member in self.member if member]
|
||||||
User.get(dn=user_id, conn=conn)
|
|
||||||
for user_id in self.member
|
|
||||||
if User.get(dn=user_id, conn=conn)
|
|
||||||
]
|
|
||||||
|
|
||||||
def add_member(self, user):
|
def add_member(self, user):
|
||||||
self.member = self.member + [user.dn]
|
self.member = self.member + [user]
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def remove_member(self, user):
|
def remove_member(self, user):
|
||||||
self.member = [m for m in self.member if m != user.dn]
|
self.member = [m for m in self.member if m != user]
|
||||||
self.save()
|
self.save()
|
||||||
|
|
|
@ -71,7 +71,7 @@ def add(user):
|
||||||
if form["token_endpoint_auth_method"].data == "none"
|
if form["token_endpoint_auth_method"].data == "none"
|
||||||
else gen_salt(48),
|
else gen_salt(48),
|
||||||
)
|
)
|
||||||
client.audience = [client.dn]
|
client.audience = [client]
|
||||||
client.save()
|
client.save()
|
||||||
flash(
|
flash(
|
||||||
_("The client has been created."),
|
_("The client has been created."),
|
||||||
|
@ -142,7 +142,7 @@ def client_edit(client_id):
|
||||||
software_version=form["software_version"].data,
|
software_version=form["software_version"].data,
|
||||||
jwk=form["jwk"].data,
|
jwk=form["jwk"].data,
|
||||||
jwks_uri=form["jwks_uri"].data,
|
jwks_uri=form["jwks_uri"].data,
|
||||||
audience=form["audience"].data,
|
audience=[Client.get(dn=dn) for dn in form["audience"].data],
|
||||||
preconsent=form["preconsent"].data,
|
preconsent=form["preconsent"].data,
|
||||||
)
|
)
|
||||||
client.save()
|
client.save()
|
||||||
|
|
|
@ -20,19 +20,17 @@ bp = Blueprint("consents", __name__, url_prefix="/consent")
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
@user_needed()
|
@user_needed()
|
||||||
def consents(user):
|
def consents(user):
|
||||||
consents = Consent.query(subject=user.dn)
|
consents = Consent.query(subject=user)
|
||||||
client_dns = {t.client for t in consents}
|
clients = {t.client for t in consents}
|
||||||
clients = {dn: Client.get(dn) for dn in client_dns}
|
|
||||||
preconsented = [
|
preconsented = [
|
||||||
client
|
client
|
||||||
for client in Client.query()
|
for client in Client.query()
|
||||||
if client.preconsent and client.dn not in clients
|
if client.preconsent and client not in clients
|
||||||
]
|
]
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"oidc/user/consent_list.html",
|
"oidc/user/consent_list.html",
|
||||||
consents=consents,
|
consents=consents,
|
||||||
clients=clients,
|
|
||||||
menuitem="consents",
|
menuitem="consents",
|
||||||
scope_details=SCOPE_DETAILS,
|
scope_details=SCOPE_DETAILS,
|
||||||
ignored_scopes=["openid"],
|
ignored_scopes=["openid"],
|
||||||
|
@ -45,7 +43,7 @@ def consents(user):
|
||||||
def revoke(user, consent_id):
|
def revoke(user, consent_id):
|
||||||
consent = Consent.get(consent_id)
|
consent = Consent.get(consent_id)
|
||||||
|
|
||||||
if not consent or consent.subject != user.dn:
|
if not consent or consent.subject != user:
|
||||||
flash(_("Could not revoke this access"), "error")
|
flash(_("Could not revoke this access"), "error")
|
||||||
|
|
||||||
elif consent.revokation_date:
|
elif consent.revokation_date:
|
||||||
|
@ -63,7 +61,7 @@ def revoke(user, consent_id):
|
||||||
def restore(user, consent_id):
|
def restore(user, consent_id):
|
||||||
consent = Consent.get(consent_id)
|
consent = Consent.get(consent_id)
|
||||||
|
|
||||||
if not consent or consent.subject != user.dn:
|
if not consent or consent.subject != user:
|
||||||
flash(_("Could not restore this access"), "error")
|
flash(_("Could not restore this access"), "error")
|
||||||
|
|
||||||
elif not consent.revokation_date:
|
elif not consent.revokation_date:
|
||||||
|
@ -88,14 +86,14 @@ def revoke_preconsent(user, client_id):
|
||||||
flash(_("Could not revoke this access"), "error")
|
flash(_("Could not revoke this access"), "error")
|
||||||
return redirect(url_for("oidc.consents.consents"))
|
return redirect(url_for("oidc.consents.consents"))
|
||||||
|
|
||||||
consent = Consent.get(client=client.dn, subject=user.dn)
|
consent = Consent.get(client=client, subject=user)
|
||||||
if consent:
|
if consent:
|
||||||
return redirect(url_for("oidc.consents.revoke", consent_id=consent.cn[0]))
|
return redirect(url_for("oidc.consents.revoke", consent_id=consent.cn[0]))
|
||||||
|
|
||||||
consent = Consent(
|
consent = Consent(
|
||||||
cn=str(uuid.uuid4()),
|
cn=str(uuid.uuid4()),
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
scope=client.scope,
|
scope=client.scope,
|
||||||
)
|
)
|
||||||
consent.revoke()
|
consent.revoke()
|
||||||
|
|
|
@ -90,8 +90,8 @@ def authorize():
|
||||||
# CONSENT
|
# CONSENT
|
||||||
|
|
||||||
consents = Consent.query(
|
consents = Consent.query(
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
)
|
)
|
||||||
consent = consents[0] if consents else None
|
consent = consents[0] if consents else None
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ def authorize():
|
||||||
or (consent and all(scope in set(consent.scope) for scope in scopes))
|
or (consent and all(scope in set(consent.scope) for scope in scopes))
|
||||||
and not consent.revoked
|
and not consent.revoked
|
||||||
):
|
):
|
||||||
return authorization.create_authorization_response(grant_user=user.dn)
|
return authorization.create_authorization_response(grant_user=user)
|
||||||
|
|
||||||
elif request.args.get("prompt") == "none":
|
elif request.args.get("prompt") == "none":
|
||||||
response = {"error": "consent_required"}
|
response = {"error": "consent_required"}
|
||||||
|
@ -135,7 +135,7 @@ def authorize():
|
||||||
grant_user = None
|
grant_user = None
|
||||||
|
|
||||||
if request.form["answer"] == "accept":
|
if request.form["answer"] == "accept":
|
||||||
grant_user = user.dn
|
grant_user = user
|
||||||
|
|
||||||
if consent:
|
if consent:
|
||||||
if consent.revoked:
|
if consent.revoked:
|
||||||
|
@ -146,8 +146,8 @@ def authorize():
|
||||||
else:
|
else:
|
||||||
consent = Consent(
|
consent = Consent(
|
||||||
cn=str(uuid.uuid4()),
|
cn=str(uuid.uuid4()),
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
scope=scopes,
|
scope=scopes,
|
||||||
issue_date=datetime.datetime.now(),
|
issue_date=datetime.datetime.now(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -96,13 +96,13 @@ class Client(LDAPObject, ClientMixin):
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
for consent in Consent.query(client=self.dn):
|
for consent in Consent.query(client=self):
|
||||||
consent.delete()
|
consent.delete()
|
||||||
|
|
||||||
for code in AuthorizationCode.query(client=self.dn):
|
for code in AuthorizationCode.query(client=self):
|
||||||
code.delete()
|
code.delete()
|
||||||
|
|
||||||
for token in Token.query(client=self.dn):
|
for token in Token.query(client=self):
|
||||||
token.delete()
|
token.delete()
|
||||||
|
|
||||||
super().delete()
|
super().delete()
|
||||||
|
@ -206,7 +206,7 @@ class Token(LDAPObject, TokenMixin):
|
||||||
return bool(self.revokation_date)
|
return bool(self.revokation_date)
|
||||||
|
|
||||||
def check_client(self, client):
|
def check_client(self, client):
|
||||||
return client.client_id == Client.get(self.client).client_id
|
return client.client_id == self.client.client_id
|
||||||
|
|
||||||
|
|
||||||
class Consent(LDAPObject):
|
class Consent(LDAPObject):
|
||||||
|
|
|
@ -43,7 +43,8 @@ AUTHORIZATION_CODE_LIFETIME = 84400
|
||||||
|
|
||||||
|
|
||||||
def exists_nonce(nonce, req):
|
def exists_nonce(nonce, req):
|
||||||
exists = AuthorizationCode.query(client=req.client_id, nonce=nonce)
|
client = Client.get(dn=req.client_id)
|
||||||
|
exists = AuthorizationCode.query(client=client, nonce=nonce)
|
||||||
return bool(exists)
|
return bool(exists)
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +99,6 @@ def claims_from_scope(scope):
|
||||||
|
|
||||||
|
|
||||||
def generate_user_info(user, scope):
|
def generate_user_info(user, scope):
|
||||||
user = User.get(dn=user)
|
|
||||||
claims = claims_from_scope(scope)
|
claims = claims_from_scope(scope)
|
||||||
data = generate_user_claims(user, claims)
|
data = generate_user_claims(user, claims)
|
||||||
return UserInfo(**data)
|
return UserInfo(**data)
|
||||||
|
@ -131,7 +131,7 @@ def save_authorization_code(code, request):
|
||||||
authorization_code_id=gen_salt(48),
|
authorization_code_id=gen_salt(48),
|
||||||
code=code,
|
code=code,
|
||||||
subject=request.user,
|
subject=request.user,
|
||||||
client=request.client.dn,
|
client=request.client,
|
||||||
redirect_uri=request.redirect_uri or request.client.redirect_uris[0],
|
redirect_uri=request.redirect_uri or request.client.redirect_uris[0],
|
||||||
scope=scope,
|
scope=scope,
|
||||||
nonce=nonce,
|
nonce=nonce,
|
||||||
|
@ -151,7 +151,7 @@ class AuthorizationCodeGrant(_AuthorizationCodeGrant):
|
||||||
return save_authorization_code(code, request)
|
return save_authorization_code(code, request)
|
||||||
|
|
||||||
def query_authorization_code(self, code, client):
|
def query_authorization_code(self, code, client):
|
||||||
item = AuthorizationCode.query(code=code, client=client.dn)
|
item = AuthorizationCode.query(code=code, client=client)
|
||||||
if item and not item[0].is_expired():
|
if item and not item[0].is_expired():
|
||||||
return item[0]
|
return item[0]
|
||||||
|
|
||||||
|
@ -159,9 +159,7 @@ class AuthorizationCodeGrant(_AuthorizationCodeGrant):
|
||||||
authorization_code.delete()
|
authorization_code.delete()
|
||||||
|
|
||||||
def authenticate_user(self, authorization_code):
|
def authenticate_user(self, authorization_code):
|
||||||
user = User.get(dn=authorization_code.subject)
|
return authorization_code.subject
|
||||||
if user:
|
|
||||||
return user.dn
|
|
||||||
|
|
||||||
|
|
||||||
class OpenIDCode(_OpenIDCode):
|
class OpenIDCode(_OpenIDCode):
|
||||||
|
@ -176,16 +174,14 @@ class OpenIDCode(_OpenIDCode):
|
||||||
|
|
||||||
def get_audiences(self, request):
|
def get_audiences(self, request):
|
||||||
client = request.client
|
client = request.client
|
||||||
return [Client.get(aud).client_id for aud in client.audience]
|
return [aud.client_id for aud in client.audience]
|
||||||
|
|
||||||
|
|
||||||
class PasswordGrant(_ResourceOwnerPasswordCredentialsGrant):
|
class PasswordGrant(_ResourceOwnerPasswordCredentialsGrant):
|
||||||
TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post", "none"]
|
TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post", "none"]
|
||||||
|
|
||||||
def authenticate_user(self, username, password):
|
def authenticate_user(self, username, password):
|
||||||
user = User.authenticate(username, password)
|
return User.authenticate(username, password)
|
||||||
if user:
|
|
||||||
return user.dn
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshTokenGrant(_RefreshTokenGrant):
|
class RefreshTokenGrant(_RefreshTokenGrant):
|
||||||
|
@ -195,9 +191,7 @@ class RefreshTokenGrant(_RefreshTokenGrant):
|
||||||
return token[0]
|
return token[0]
|
||||||
|
|
||||||
def authenticate_user(self, credential):
|
def authenticate_user(self, credential):
|
||||||
user = User.get(dn=credential.subject)
|
return credential.subject
|
||||||
if user:
|
|
||||||
return user.dn
|
|
||||||
|
|
||||||
def revoke_old_credential(self, credential):
|
def revoke_old_credential(self, credential):
|
||||||
credential.revokation_date = datetime.datetime.now()
|
credential.revokation_date = datetime.datetime.now()
|
||||||
|
@ -216,7 +210,7 @@ class OpenIDImplicitGrant(_OpenIDImplicitGrant):
|
||||||
|
|
||||||
def get_audiences(self, request):
|
def get_audiences(self, request):
|
||||||
client = request.client
|
client = request.client
|
||||||
return [Client.get(aud).client_id for aud in client.audience]
|
return [aud.client_id for aud in client.audience]
|
||||||
|
|
||||||
|
|
||||||
class OpenIDHybridGrant(_OpenIDHybridGrant):
|
class OpenIDHybridGrant(_OpenIDHybridGrant):
|
||||||
|
@ -234,7 +228,7 @@ class OpenIDHybridGrant(_OpenIDHybridGrant):
|
||||||
|
|
||||||
def get_audiences(self, request):
|
def get_audiences(self, request):
|
||||||
client = request.client
|
client = request.client
|
||||||
return [Client.get(aud).client_id for aud in client.audience]
|
return [aud.client_id for aud in client.audience]
|
||||||
|
|
||||||
|
|
||||||
def query_client(client_id):
|
def query_client(client_id):
|
||||||
|
@ -250,7 +244,7 @@ def save_token(token, request):
|
||||||
issue_date=now,
|
issue_date=now,
|
||||||
lifetime=token["expires_in"],
|
lifetime=token["expires_in"],
|
||||||
scope=token["scope"],
|
scope=token["scope"],
|
||||||
client=request.client.dn,
|
client=request.client,
|
||||||
refresh_token=token.get("refresh_token"),
|
refresh_token=token.get("refresh_token"),
|
||||||
subject=request.user,
|
subject=request.user,
|
||||||
audience=request.client.audience,
|
audience=request.client.audience,
|
||||||
|
@ -294,19 +288,17 @@ class IntrospectionEndpoint(_IntrospectionEndpoint):
|
||||||
return query_token(token, token_type_hint)
|
return query_token(token, token_type_hint)
|
||||||
|
|
||||||
def check_permission(self, token, client, request):
|
def check_permission(self, token, client, request):
|
||||||
return client.dn in token.audience
|
return client in token.audience
|
||||||
|
|
||||||
def introspect_token(self, token):
|
def introspect_token(self, token):
|
||||||
client_id = Client.get(token.client).client_id
|
audience = [aud.client_id for aud in token.audience]
|
||||||
user = User.get(dn=token.subject)
|
|
||||||
audience = [Client.get(aud).client_id for aud in token.audience]
|
|
||||||
return {
|
return {
|
||||||
"active": True,
|
"active": True,
|
||||||
"client_id": client_id,
|
"client_id": token.client.client_id,
|
||||||
"token_type": token.type,
|
"token_type": token.type,
|
||||||
"username": user.name,
|
"username": token.subject.name,
|
||||||
"scope": token.get_scope(),
|
"scope": token.get_scope(),
|
||||||
"sub": user.uid[0],
|
"sub": token.subject.uid[0],
|
||||||
"aud": audience,
|
"aud": audience,
|
||||||
"iss": get_issuer(),
|
"iss": get_issuer(),
|
||||||
"exp": token.get_expires_at(),
|
"exp": token.get_expires_at(),
|
||||||
|
@ -402,7 +394,7 @@ require_oauth = ResourceProtector()
|
||||||
|
|
||||||
|
|
||||||
def generate_access_token(client, grant_type, user, scope):
|
def generate_access_token(client, grant_type, user, scope):
|
||||||
audience = [Client.get(dn).client_id for dn in client.audience]
|
audience = [client.client_id for client in client.audience]
|
||||||
bearer_token_generator = authorization._token_generators["default"]
|
bearer_token_generator = authorization._token_generators["default"]
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"token": {},
|
"token": {},
|
||||||
|
|
|
@ -20,11 +20,9 @@ bp = Blueprint("tokens", __name__, url_prefix="/admin/token")
|
||||||
@permissions_needed("manage_oidc")
|
@permissions_needed("manage_oidc")
|
||||||
def index(user):
|
def index(user):
|
||||||
tokens = Token.query()
|
tokens = Token.query()
|
||||||
items = (
|
return render_template(
|
||||||
(token, Client.get(token.client), User.get(dn=token.subject))
|
"oidc/admin/token_list.html", tokens=tokens, menuitem="admin"
|
||||||
for token in tokens
|
|
||||||
)
|
)
|
||||||
return render_template("oidc/admin/token_list.html", items=items, menuitem="admin")
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/<token_id>", methods=["GET", "POST"])
|
@bp.route("/<token_id>", methods=["GET", "POST"])
|
||||||
|
@ -35,15 +33,9 @@ def view(user, token_id):
|
||||||
if not token:
|
if not token:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
token_client = Client.get(token.client)
|
|
||||||
token_user = User.get(dn=token.subject)
|
|
||||||
token_audience = [Client.get(aud) for aud in token.audience]
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"oidc/admin/token_view.html",
|
"oidc/admin/token_view.html",
|
||||||
token=token,
|
token=token,
|
||||||
token_client=token_client,
|
|
||||||
token_user=token_user,
|
|
||||||
token_audience=token_audience,
|
|
||||||
menuitem="admin",
|
menuitem="admin",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ def fake_groups(nb=1, nb_users_max=1):
|
||||||
description=fake.sentence(),
|
description=fake.sentence(),
|
||||||
)
|
)
|
||||||
nb_users = random.randrange(1, nb_users_max + 1)
|
nb_users = random.randrange(1, nb_users_max + 1)
|
||||||
group.member = list({random.choice(users).dn for _ in range(nb_users)})
|
group.member = list({random.choice(users) for _ in range(nb_users)})
|
||||||
group.save()
|
group.save()
|
||||||
groups.append(group)
|
groups.append(group)
|
||||||
return groups
|
return groups
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<th>{% trans %}Subject{% endtrans %}</th>
|
<th>{% trans %}Subject{% endtrans %}</th>
|
||||||
<th>{% trans %}Created{% endtrans %}</th>
|
<th>{% trans %}Created{% endtrans %}</th>
|
||||||
</thead>
|
</thead>
|
||||||
{% for token, client, user in items %}
|
{% for token in tokens %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('oidc.tokens.view', token_id=token.token_id) }}">
|
<a href="{{ url_for('oidc.tokens.view', token_id=token.token_id) }}">
|
||||||
|
@ -28,13 +28,13 @@
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('oidc.clients.edit', client_id=client.client_id) }}">
|
<a href="{{ url_for('oidc.clients.edit', client_id=token.client.client_id) }}">
|
||||||
{{ client.client_name }}
|
{{ token.client.client_name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for("account.profile_edition", username=user.uid[0]) }}">
|
<a href="{{ url_for("account.profile_edition", username=token.subject.uid[0]) }}">
|
||||||
{{ user.uid[0] }}
|
{{ token.subject.uid[0] }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td data-order="{{ token.issue_date|timestamp }}">{{ token.issue_date }}</td>
|
<td data-order="{{ token.issue_date|timestamp }}">{{ token.issue_date }}</td>
|
||||||
|
|
|
@ -33,16 +33,16 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ _("Client") }}</td>
|
<td>{{ _("Client") }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for("oidc.clients.edit", client_id=token_client.client_id) }}">
|
<a href="{{ url_for("oidc.clients.edit", client_id=token.client.client_id) }}">
|
||||||
{{ token_client.client_name }}
|
{{ token.client.client_name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ _("Subject") }}</td>
|
<td>{{ _("Subject") }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for("account.profile_edition", username=token_user.uid[0]) }}">
|
<a href="{{ url_for("account.profile_edition", username=token.subject.uid[0]) }}">
|
||||||
{{ token_user.name }} - {{ token_user.uid[0] }}
|
{{ token.subject.name }} - {{ token.subject.uid[0] }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
<td>{{ _("Audience") }}</td>
|
<td>{{ _("Audience") }}</td>
|
||||||
<td>
|
<td>
|
||||||
<ul class="ui list">
|
<ul class="ui list">
|
||||||
{% for client in token_audience %}
|
{% for client in token.audience %}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<a href="{{ url_for("oidc.clients.edit", client_id=client.dn) }}">
|
<a href="{{ url_for("oidc.clients.edit", client_id=client.dn) }}">
|
||||||
{{ client.client_name }}
|
{{ client.client_name }}
|
||||||
|
|
|
@ -25,16 +25,15 @@
|
||||||
{% if consents %}
|
{% if consents %}
|
||||||
<div class="ui centered cards">
|
<div class="ui centered cards">
|
||||||
{% for consent in consents %}
|
{% for consent in consents %}
|
||||||
{% set client = clients[consent.client] %}
|
|
||||||
<div class="ui card">
|
<div class="ui card">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{% if client.logo_uri %}
|
{% if consent.client.logo_uri %}
|
||||||
<img class="right floated mini ui image" src="{{ client.logo_uri }}">
|
<img class="right floated mini ui image" src="{{ consent.client.logo_uri }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if client.client_uri %}
|
{% if consent.client.client_uri %}
|
||||||
<a href="{{ client.client_uri }}" class="header">{{ client.client_name }}</a>
|
<a href="{{ consent.client.client_uri }}" class="header">{{ consent.client.client_name }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="header">{{ client.client_name }}</div>
|
<div class="header">{{ consent.client.client_name }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if consent.issue_date %}
|
{% if consent.issue_date %}
|
||||||
<div class="meta">{% trans %}From:{% endtrans %} {{ consent.issue_date.strftime("%d/%m/%Y %H:%M:%S") }}</div>
|
<div class="meta">{% trans %}From:{% endtrans %} {{ consent.issue_date.strftime("%d/%m/%Y %H:%M:%S") }}</div>
|
||||||
|
@ -66,18 +65,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if client.tos_uri or client.policy_uri %}
|
{% if consent.client.tos_uri or consent.client.policy_uri %}
|
||||||
<div class="extra content">
|
<div class="extra content">
|
||||||
{% if client.policy_uri %}
|
{% if consent.client.policy_uri %}
|
||||||
<span class="right floated">
|
<span class="right floated">
|
||||||
<i class="mask icon"></i>
|
<i class="mask icon"></i>
|
||||||
<a href="{{ client.policy_uri }}">{% trans %}Policy{% endtrans %}</a>
|
<a href="{{ consent.client.policy_uri }}">{% trans %}Policy{% endtrans %}</a>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if client.tos_uri %}
|
{% if consent.client.tos_uri %}
|
||||||
<span>
|
<span>
|
||||||
<i class="file signature icon"></i>
|
<i class="file signature icon"></i>
|
||||||
<a href="{{ client.tos_uri }}">{% trans %}Terms of service{% endtrans %}</a>
|
<a href="{{ consent.client.tos_uri }}">{% trans %}Terms of service{% endtrans %}</a>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -233,7 +233,7 @@ def logged_moderator(moderator, testclient):
|
||||||
def foo_group(app, user, slapd_connection):
|
def foo_group(app, user, slapd_connection):
|
||||||
Group.ldap_object_classes(slapd_connection)
|
Group.ldap_object_classes(slapd_connection)
|
||||||
group = Group(
|
group = Group(
|
||||||
member=[user.dn],
|
member=[user],
|
||||||
cn="foo",
|
cn="foo",
|
||||||
)
|
)
|
||||||
group.save()
|
group.save()
|
||||||
|
@ -247,7 +247,7 @@ def foo_group(app, user, slapd_connection):
|
||||||
def bar_group(app, admin, slapd_connection):
|
def bar_group(app, admin, slapd_connection):
|
||||||
Group.ldap_object_classes(slapd_connection)
|
Group.ldap_object_classes(slapd_connection)
|
||||||
group = Group(
|
group = Group(
|
||||||
member=[admin.dn],
|
member=[admin],
|
||||||
cn="bar",
|
cn="bar",
|
||||||
)
|
)
|
||||||
group.save()
|
group.save()
|
||||||
|
|
|
@ -117,3 +117,15 @@ def test_operational_attribute_conversion(slapd_connection):
|
||||||
"oauthClientName": [b"foobar_name"],
|
"oauthClientName": [b"foobar_name"],
|
||||||
"invalidAttribute": [b"foobar"],
|
"invalidAttribute": [b"foobar"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_guess_object_from_dn(slapd_connection, testclient, foo_group):
|
||||||
|
foo_group.member = [foo_group]
|
||||||
|
foo_group.save()
|
||||||
|
g = LDAPObject.get(dn=foo_group.dn)
|
||||||
|
assert isinstance(g, Group)
|
||||||
|
assert g == foo_group
|
||||||
|
assert g.cn == foo_group.cn
|
||||||
|
|
||||||
|
ou = LDAPObject.get(dn=f"{Group.base},{Group.root_dn}")
|
||||||
|
assert isinstance(g, LDAPObject)
|
||||||
|
|
|
@ -109,7 +109,7 @@ def client(testclient, other_client, slapd_connection):
|
||||||
token_endpoint_auth_method="client_secret_basic",
|
token_endpoint_auth_method="client_secret_basic",
|
||||||
post_logout_redirect_uris=["https://mydomain.tld/disconnected"],
|
post_logout_redirect_uris=["https://mydomain.tld/disconnected"],
|
||||||
)
|
)
|
||||||
c.audience = [c.dn, other_client.dn]
|
c.audience = [c, other_client]
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
yield c
|
yield c
|
||||||
|
@ -145,7 +145,7 @@ def other_client(testclient, slapd_connection):
|
||||||
token_endpoint_auth_method="client_secret_basic",
|
token_endpoint_auth_method="client_secret_basic",
|
||||||
post_logout_redirect_uris=["https://myotherdomain.tld/disconnected"],
|
post_logout_redirect_uris=["https://myotherdomain.tld/disconnected"],
|
||||||
)
|
)
|
||||||
c.audience = [c.dn]
|
c.audience = [c]
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
yield c
|
yield c
|
||||||
|
@ -157,8 +157,8 @@ def authorization(testclient, user, client, slapd_connection):
|
||||||
a = AuthorizationCode(
|
a = AuthorizationCode(
|
||||||
authorization_code_id=gen_salt(48),
|
authorization_code_id=gen_salt(48),
|
||||||
code="my-code",
|
code="my-code",
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
redirect_uri="https://foo.bar/callback",
|
redirect_uri="https://foo.bar/callback",
|
||||||
response_type="code",
|
response_type="code",
|
||||||
scope="openid profile",
|
scope="openid profile",
|
||||||
|
@ -179,9 +179,9 @@ def token(testclient, client, user, slapd_connection):
|
||||||
t = Token(
|
t = Token(
|
||||||
token_id=gen_salt(48),
|
token_id=gen_salt(48),
|
||||||
access_token=gen_salt(48),
|
access_token=gen_salt(48),
|
||||||
audience=[client.dn],
|
audience=[client],
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
token_type=None,
|
token_type=None,
|
||||||
refresh_token=gen_salt(48),
|
refresh_token=gen_salt(48),
|
||||||
scope="openid profile",
|
scope="openid profile",
|
||||||
|
@ -197,7 +197,7 @@ def token(testclient, client, user, slapd_connection):
|
||||||
def id_token(testclient, client, user, slapd_connection):
|
def id_token(testclient, client, user, slapd_connection):
|
||||||
return generate_id_token(
|
return generate_id_token(
|
||||||
{},
|
{},
|
||||||
generate_user_info(user.dn, client.scope),
|
generate_user_info(user, client.scope),
|
||||||
aud=client.client_id,
|
aud=client.client_id,
|
||||||
**get_jwt_config(None)
|
**get_jwt_config(None)
|
||||||
)
|
)
|
||||||
|
@ -207,8 +207,8 @@ def id_token(testclient, client, user, slapd_connection):
|
||||||
def consent(testclient, client, user, slapd_connection):
|
def consent(testclient, client, user, slapd_connection):
|
||||||
t = Consent(
|
t = Consent(
|
||||||
cn=str(uuid.uuid4()),
|
cn=str(uuid.uuid4()),
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
scope=["openid", "profile"],
|
scope=["openid", "profile"],
|
||||||
issue_date=datetime.datetime.now(),
|
issue_date=datetime.datetime.now(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,7 +47,7 @@ def test_authorization_code_flow(
|
||||||
"phone",
|
"phone",
|
||||||
}
|
}
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert set(consents[0].scope) == {
|
assert set(consents[0].scope) == {
|
||||||
"openid",
|
"openid",
|
||||||
"profile",
|
"profile",
|
||||||
|
@ -71,8 +71,8 @@ def test_authorization_code_flow(
|
||||||
|
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
assert set(token.scope[0].split(" ")) == {
|
assert set(token.scope[0].split(" ")) == {
|
||||||
"openid",
|
"openid",
|
||||||
"profile",
|
"profile",
|
||||||
|
@ -140,7 +140,7 @@ def test_authorization_code_flow_with_redirect_uri(
|
||||||
code = params["code"][0]
|
code = params["code"][0]
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
|
|
||||||
res = testclient.post(
|
res = testclient.post(
|
||||||
"/oauth/token",
|
"/oauth/token",
|
||||||
|
@ -156,8 +156,8 @@ def test_authorization_code_flow_with_redirect_uri(
|
||||||
|
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
|
|
||||||
for consent in consents:
|
for consent in consents:
|
||||||
consent.delete()
|
consent.delete()
|
||||||
|
@ -188,7 +188,7 @@ def test_authorization_code_flow_preconsented(
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert not consents
|
assert not consents
|
||||||
|
|
||||||
res = testclient.post(
|
res = testclient.post(
|
||||||
|
@ -205,8 +205,8 @@ def test_authorization_code_flow_preconsented(
|
||||||
|
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
|
|
||||||
id_token = res.json["id_token"]
|
id_token = res.json["id_token"]
|
||||||
claims = jwt.decode(id_token, keypair[1])
|
claims = jwt.decode(id_token, keypair[1])
|
||||||
|
@ -257,7 +257,7 @@ def test_logout_login(testclient, logged_user, client):
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert "profile" in consents[0].scope
|
assert "profile" in consents[0].scope
|
||||||
|
|
||||||
res = testclient.post(
|
res = testclient.post(
|
||||||
|
@ -274,8 +274,8 @@ def test_logout_login(testclient, logged_user, client):
|
||||||
|
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
|
|
||||||
res = testclient.get(
|
res = testclient.get(
|
||||||
"/oauth/userinfo",
|
"/oauth/userinfo",
|
||||||
|
@ -338,7 +338,7 @@ def test_refresh_token(testclient, user, client):
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=user.dn)
|
consents = Consent.query(client=client, subject=user)
|
||||||
assert "profile" in consents[0].scope
|
assert "profile" in consents[0].scope
|
||||||
|
|
||||||
with freezegun.freeze_time("2020-01-01 00:01:00"):
|
with freezegun.freeze_time("2020-01-01 00:01:00"):
|
||||||
|
@ -418,7 +418,7 @@ def test_code_challenge(testclient, logged_user, client):
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert "profile" in consents[0].scope
|
assert "profile" in consents[0].scope
|
||||||
|
|
||||||
res = testclient.post(
|
res = testclient.post(
|
||||||
|
@ -436,8 +436,8 @@ def test_code_challenge(testclient, logged_user, client):
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
|
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
|
|
||||||
res = testclient.get(
|
res = testclient.get(
|
||||||
"/oauth/userinfo",
|
"/oauth/userinfo",
|
||||||
|
@ -477,7 +477,7 @@ def test_authorization_code_flow_when_consent_already_given(
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert "profile" in consents[0].scope
|
assert "profile" in consents[0].scope
|
||||||
|
|
||||||
res = testclient.post(
|
res = testclient.post(
|
||||||
|
@ -535,7 +535,7 @@ def test_authorization_code_flow_when_consent_already_given_but_for_a_smaller_sc
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert "profile" in consents[0].scope
|
assert "profile" in consents[0].scope
|
||||||
assert "groups" not in consents[0].scope
|
assert "groups" not in consents[0].scope
|
||||||
|
|
||||||
|
@ -571,7 +571,7 @@ def test_authorization_code_flow_when_consent_already_given_but_for_a_smaller_sc
|
||||||
authcode = AuthorizationCode.get(code=code)
|
authcode = AuthorizationCode.get(code=code)
|
||||||
assert authcode is not None
|
assert authcode is not None
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
assert "profile" in consents[0].scope
|
assert "profile" in consents[0].scope
|
||||||
assert "groups" in consents[0].scope
|
assert "groups" in consents[0].scope
|
||||||
|
|
||||||
|
@ -606,8 +606,8 @@ def test_authorization_code_flow_but_user_cannot_use_oidc(
|
||||||
def test_prompt_none(testclient, logged_user, client):
|
def test_prompt_none(testclient, logged_user, client):
|
||||||
consent = Consent(
|
consent = Consent(
|
||||||
cn=str(uuid.uuid4()),
|
cn=str(uuid.uuid4()),
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=logged_user.dn,
|
subject=logged_user,
|
||||||
scope=["openid", "profile"],
|
scope=["openid", "profile"],
|
||||||
)
|
)
|
||||||
consent.save()
|
consent.save()
|
||||||
|
@ -633,8 +633,8 @@ def test_prompt_none(testclient, logged_user, client):
|
||||||
def test_prompt_not_logged(testclient, user, client):
|
def test_prompt_not_logged(testclient, user, client):
|
||||||
consent = Consent(
|
consent = Consent(
|
||||||
cn=str(uuid.uuid4()),
|
cn=str(uuid.uuid4()),
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
scope=["openid", "profile"],
|
scope=["openid", "profile"],
|
||||||
)
|
)
|
||||||
consent.save()
|
consent.save()
|
||||||
|
@ -732,7 +732,7 @@ def test_authorization_code_request_scope_too_large(
|
||||||
"profile",
|
"profile",
|
||||||
}
|
}
|
||||||
|
|
||||||
consents = Consent.query(client=other_client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=other_client, subject=logged_user)
|
||||||
assert set(consents[0].scope) == {
|
assert set(consents[0].scope) == {
|
||||||
"openid",
|
"openid",
|
||||||
"profile",
|
"profile",
|
||||||
|
@ -752,8 +752,8 @@ def test_authorization_code_request_scope_too_large(
|
||||||
|
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == other_client.dn
|
assert token.client == other_client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
assert set(token.scope[0].split(" ")) == {
|
assert set(token.scope[0].split(" ")) == {
|
||||||
"openid",
|
"openid",
|
||||||
"profile",
|
"profile",
|
||||||
|
@ -965,7 +965,7 @@ def test_token_default_expiration_date(testclient, logged_user, client, keypair)
|
||||||
claims = jwt.decode(id_token, keypair[1])
|
claims = jwt.decode(id_token, keypair[1])
|
||||||
assert claims["exp"] - claims["iat"] == 3600
|
assert claims["exp"] - claims["iat"] == 3600
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
for consent in consents:
|
for consent in consents:
|
||||||
consent.delete()
|
consent.delete()
|
||||||
|
|
||||||
|
@ -1023,6 +1023,6 @@ def test_token_custom_expiration_date(testclient, logged_user, client, keypair):
|
||||||
claims = jwt.decode(id_token, keypair[1])
|
claims = jwt.decode(id_token, keypair[1])
|
||||||
assert claims["exp"] - claims["iat"] == 6000
|
assert claims["exp"] - claims["iat"] == 6000
|
||||||
|
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
for consent in consents:
|
for consent in consents:
|
||||||
consent.delete()
|
consent.delete()
|
||||||
|
|
|
@ -11,8 +11,8 @@ def test_clean_command(testclient, slapd_connection, client, user):
|
||||||
valid_code = AuthorizationCode(
|
valid_code = AuthorizationCode(
|
||||||
authorization_code_id=gen_salt(48),
|
authorization_code_id=gen_salt(48),
|
||||||
code="my-valid-code",
|
code="my-valid-code",
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
redirect_uri="https://foo.bar/callback",
|
redirect_uri="https://foo.bar/callback",
|
||||||
response_type="code",
|
response_type="code",
|
||||||
scope="openid profile",
|
scope="openid profile",
|
||||||
|
@ -27,8 +27,8 @@ def test_clean_command(testclient, slapd_connection, client, user):
|
||||||
expired_code = AuthorizationCode(
|
expired_code = AuthorizationCode(
|
||||||
authorization_code_id=gen_salt(48),
|
authorization_code_id=gen_salt(48),
|
||||||
code="my-expired-code",
|
code="my-expired-code",
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
redirect_uri="https://foo.bar/callback",
|
redirect_uri="https://foo.bar/callback",
|
||||||
response_type="code",
|
response_type="code",
|
||||||
scope="openid profile",
|
scope="openid profile",
|
||||||
|
@ -47,8 +47,8 @@ def test_clean_command(testclient, slapd_connection, client, user):
|
||||||
valid_token = Token(
|
valid_token = Token(
|
||||||
token_id=gen_salt(48),
|
token_id=gen_salt(48),
|
||||||
access_token="my-valid-token",
|
access_token="my-valid-token",
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
type=None,
|
type=None,
|
||||||
refresh_token=gen_salt(48),
|
refresh_token=gen_salt(48),
|
||||||
scope="openid profile",
|
scope="openid profile",
|
||||||
|
@ -59,8 +59,8 @@ def test_clean_command(testclient, slapd_connection, client, user):
|
||||||
expired_token = Token(
|
expired_token = Token(
|
||||||
token_id=gen_salt(48),
|
token_id=gen_salt(48),
|
||||||
access_token="my-expired-token",
|
access_token="my-expired-token",
|
||||||
client=client.dn,
|
client=client,
|
||||||
subject=user.dn,
|
subject=user,
|
||||||
type=None,
|
type=None,
|
||||||
refresh_token=gen_salt(48),
|
refresh_token=gen_salt(48),
|
||||||
scope="openid profile",
|
scope="openid profile",
|
||||||
|
|
|
@ -53,7 +53,7 @@ def test_client_add(testclient, logged_admin):
|
||||||
|
|
||||||
client_id = res.forms["readonly"]["client_id"].value
|
client_id = res.forms["readonly"]["client_id"].value
|
||||||
client = Client.get(client_id)
|
client = Client.get(client_id)
|
||||||
data["audience"] = [client.dn]
|
data["audience"] = [client]
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
client_value = getattr(client, k)
|
client_value = getattr(client, k)
|
||||||
if k == "scope":
|
if k == "scope":
|
||||||
|
@ -106,6 +106,7 @@ def test_client_edit(testclient, client, logged_admin, other_client):
|
||||||
assert ("success", "The client has been edited.") in res.flashes
|
assert ("success", "The client has been edited.") in res.flashes
|
||||||
|
|
||||||
client = Client.get(client.dn)
|
client = Client.get(client.dn)
|
||||||
|
data["audience"] = [client, other_client]
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
client_value = getattr(client, k)
|
client_value = getattr(client, k)
|
||||||
if k == "scope":
|
if k == "scope":
|
||||||
|
@ -131,16 +132,12 @@ def test_client_delete(testclient, logged_admin):
|
||||||
client = Client(client_id="client_id")
|
client = Client(client_id="client_id")
|
||||||
client.save()
|
client.save()
|
||||||
token = Token(
|
token = Token(
|
||||||
token_id="id", client=client.dn, issue_datetime=datetime.datetime.utcnow()
|
token_id="id", client=client, issue_datetime=datetime.datetime.utcnow()
|
||||||
)
|
)
|
||||||
token.save()
|
token.save()
|
||||||
consent = Consent(
|
consent = Consent(cn="cn", subject=logged_admin, client=client, scope="openid")
|
||||||
cn="cn", subject=logged_admin.dn, client=client.dn, scope="openid"
|
|
||||||
)
|
|
||||||
consent.save()
|
consent.save()
|
||||||
code = AuthorizationCode(
|
code = AuthorizationCode(authorization_code_id="id", client=client, subject=client)
|
||||||
authorization_code_id="id", client=client.dn, subject=client.dn
|
|
||||||
)
|
|
||||||
|
|
||||||
res = testclient.get("/admin/client/edit/" + client.client_id)
|
res = testclient.get("/admin/client/edit/" + client.client_id)
|
||||||
res = res.forms["clientadd"].submit(name="action", value="delete").follow()
|
res = res.forms["clientadd"].submit(name="action", value="delete").follow()
|
||||||
|
|
|
@ -118,10 +118,9 @@ def test_oidc_authorization_after_revokation(
|
||||||
|
|
||||||
res = res.form.submit(name="answer", value="accept", status=302)
|
res = res.form.submit(name="answer", value="accept", status=302)
|
||||||
|
|
||||||
Consent.query()
|
consents = Consent.query(client=client, subject=logged_user)
|
||||||
consents = Consent.query(client=client.dn, subject=logged_user.dn)
|
|
||||||
assert consents[0].dn == consent.dn
|
|
||||||
consent.reload()
|
consent.reload()
|
||||||
|
assert consents[0] == consent
|
||||||
assert not consent.revoked
|
assert not consent.revoked
|
||||||
|
|
||||||
params = parse_qs(urlsplit(res.location).query)
|
params = parse_qs(urlsplit(res.location).query)
|
||||||
|
@ -140,8 +139,8 @@ def test_oidc_authorization_after_revokation(
|
||||||
|
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
|
|
||||||
|
|
||||||
def test_preconsented_client_appears_in_consent_list(testclient, client, logged_user):
|
def test_preconsented_client_appears_in_consent_list(testclient, client, logged_user):
|
||||||
|
@ -166,8 +165,8 @@ def test_revoke_preconsented_client(testclient, client, logged_user, token):
|
||||||
assert ("success", "The access has been revoked") in res.flashes
|
assert ("success", "The access has been revoked") in res.flashes
|
||||||
|
|
||||||
consent = Consent.get()
|
consent = Consent.get()
|
||||||
assert consent.client == client.dn
|
assert consent.client == client
|
||||||
assert consent.subject == logged_user.dn
|
assert consent.subject == logged_user
|
||||||
assert consent.scope == ["openid", "email", "profile", "groups", "address", "phone"]
|
assert consent.scope == ["openid", "email", "profile", "groups", "address", "phone"]
|
||||||
assert not consent.issue_date
|
assert not consent.issue_date
|
||||||
token.reload()
|
token.reload()
|
||||||
|
|
|
@ -159,7 +159,7 @@ def test_end_session_invalid_client_id(
|
||||||
def test_client_hint_invalid(testclient, slapd_connection, logged_user, client):
|
def test_client_hint_invalid(testclient, slapd_connection, logged_user, client):
|
||||||
id_token = generate_id_token(
|
id_token = generate_id_token(
|
||||||
{},
|
{},
|
||||||
generate_user_info(logged_user.dn, client.scope),
|
generate_user_info(logged_user, client.scope),
|
||||||
aud="invalid-client-id",
|
aud="invalid-client-id",
|
||||||
**get_jwt_config(None),
|
**get_jwt_config(None),
|
||||||
)
|
)
|
||||||
|
@ -266,7 +266,7 @@ def test_jwt_not_issued_here(
|
||||||
def test_client_hint_mismatch(testclient, slapd_connection, logged_user, client):
|
def test_client_hint_mismatch(testclient, slapd_connection, logged_user, client):
|
||||||
id_token = generate_id_token(
|
id_token = generate_id_token(
|
||||||
{},
|
{},
|
||||||
generate_user_info(logged_user.dn, client.scope),
|
generate_user_info(logged_user, client.scope),
|
||||||
aud="another_client_id",
|
aud="another_client_id",
|
||||||
**get_jwt_config(None),
|
**get_jwt_config(None),
|
||||||
)
|
)
|
||||||
|
@ -299,7 +299,7 @@ def test_bad_user_id_token_mismatch(
|
||||||
|
|
||||||
id_token = generate_id_token(
|
id_token = generate_id_token(
|
||||||
{},
|
{},
|
||||||
generate_user_info(admin.dn, client.scope),
|
generate_user_info(admin, client.scope),
|
||||||
aud=client.client_id,
|
aud=client.client_id,
|
||||||
**get_jwt_config(None),
|
**get_jwt_config(None),
|
||||||
)
|
)
|
||||||
|
|
|
@ -93,8 +93,8 @@ def test_full_flow(testclient, logged_user, client, user, other_client):
|
||||||
access_token = res.json["access_token"]
|
access_token = res.json["access_token"]
|
||||||
|
|
||||||
token = Token.get(access_token=access_token)
|
token = Token.get(access_token=access_token)
|
||||||
assert token.client == client.dn
|
assert token.client == client
|
||||||
assert token.subject == logged_user.dn
|
assert token.subject == logged_user
|
||||||
|
|
||||||
res = testclient.post(
|
res = testclient.post(
|
||||||
"/oauth/introspect",
|
"/oauth/introspect",
|
||||||
|
|
|
@ -126,15 +126,13 @@ def test_get_members_filters_non_existent_user(
|
||||||
testclient, logged_moderator, foo_group, user
|
testclient, logged_moderator, foo_group, user
|
||||||
):
|
):
|
||||||
# an LDAP group can be inconsistent by containing members which doesn't exist
|
# an LDAP group can be inconsistent by containing members which doesn't exist
|
||||||
non_existent_user_id = user.dn.replace(user.name, "yolo")
|
non_existent_user = User(cn="foo", sn="bar")
|
||||||
foo_group.member = foo_group.member + [non_existent_user_id]
|
foo_group.member = foo_group.member + [non_existent_user]
|
||||||
foo_group.save()
|
foo_group.save()
|
||||||
|
|
||||||
foo_members = foo_group.get_members()
|
foo_group.get_members()
|
||||||
|
|
||||||
assert foo_group.member == [user.dn, non_existent_user_id]
|
assert foo_group.member == [user, non_existent_user]
|
||||||
assert len(foo_members) == 1
|
|
||||||
assert foo_members[0].dn == user.dn
|
|
||||||
|
|
||||||
testclient.get("/groups/foo", status=200)
|
testclient.get("/groups/foo", status=200)
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,8 @@ def test_edition(
|
||||||
("cn=bar,ou=groups,dc=mydomain,dc=tld", False, "bar"),
|
("cn=bar,ou=groups,dc=mydomain,dc=tld", False, "bar"),
|
||||||
}
|
}
|
||||||
assert logged_user.groups == [foo_group]
|
assert logged_user.groups == [foo_group]
|
||||||
assert foo_group.member == [logged_user.dn]
|
assert foo_group.member == [logged_user]
|
||||||
assert bar_group.member == [admin.dn]
|
assert bar_group.member == [admin]
|
||||||
assert res.form["groups"].attrs["readonly"]
|
assert res.form["groups"].attrs["readonly"]
|
||||||
assert res.form["uid"].attrs["readonly"]
|
assert res.form["uid"].attrs["readonly"]
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ def test_edition(
|
||||||
foo_group.reload()
|
foo_group.reload()
|
||||||
bar_group.reload()
|
bar_group.reload()
|
||||||
assert logged_user.groups == [foo_group]
|
assert logged_user.groups == [foo_group]
|
||||||
assert foo_group.member == [logged_user.dn]
|
assert foo_group.member == [logged_user]
|
||||||
assert bar_group.member == [admin.dn]
|
assert bar_group.member == [admin]
|
||||||
|
|
||||||
assert logged_user.check_password("correct horse battery staple")
|
assert logged_user.check_password("correct horse battery staple")
|
||||||
|
|
||||||
|
@ -285,8 +285,8 @@ def test_user_creation_edition_and_deletion(
|
||||||
|
|
||||||
foo_group.reload()
|
foo_group.reload()
|
||||||
bar_group.reload()
|
bar_group.reload()
|
||||||
assert george.dn in set(foo_group.member)
|
assert george in set(foo_group.member)
|
||||||
assert george.dn in set(bar_group.member)
|
assert george in set(bar_group.member)
|
||||||
assert set(george.groups) == {foo_group, bar_group}
|
assert set(george.groups) == {foo_group, bar_group}
|
||||||
assert "george" in testclient.get("/users", status=200).text
|
assert "george" in testclient.get("/users", status=200).text
|
||||||
assert "george" in testclient.get("/users", status=200).text
|
assert "george" in testclient.get("/users", status=200).text
|
||||||
|
|
Loading…
Reference in a new issue