def test_set_groups(app, slapd_connection, user, foo_group, bar_group): with app.app_context(): Group.ldap_object_attributes(conn=slapd_connection) User.ldap_object_attributes(conn=slapd_connection) user = User.get(dn=user.dn, conn=slapd_connection) user.load_groups(conn=slapd_connection) assert set(Group.available_groups(conn=slapd_connection)) == { ("foo", foo_group.dn), ("bar", bar_group.dn), } foo_dns = {g.dn for g in foo_group.get_members(conn=slapd_connection)} assert user.dn in foo_dns assert user.groups[0].dn == foo_group.dn user.set_groups([foo_group, bar_group], conn=slapd_connection) bar_dns = {g.dn for g in bar_group.get_members(conn=slapd_connection)} assert user.dn in bar_dns assert user.groups[1].dn == bar_group.dn user.set_groups([foo_group], conn=slapd_connection) foo_dns = {g.dn for g in foo_group.get_members(conn=slapd_connection)} bar_dns = {g.dn for g in bar_group.get_members(conn=slapd_connection)} assert user.dn in foo_dns assert user.dn not in bar_dns
def test_custom_config_format_claim_is_well_formated(testclient, slapd_connection, user): User.ldap_object_classes(slapd_connection) jwt_mapping_config = DEFAULT_JWT_MAPPING_CONFIG.copy() jwt_mapping_config["EMAIL"] = "{{ user.uid[0] }}@mydomain.tld" with testclient.app.app_context(): data = generate_user_claims(user, STANDARD_CLAIMS, jwt_mapping_config) assert data["email"] == "*****@*****.**"
def test_claim_is_omitted_if_empty(testclient, slapd_connection, user): # According to https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse # it's better to not insert a null or empty string value User.ldap_object_classes(slapd_connection) user.mail = "" user.save(slapd_connection) with testclient.app.app_context(): data = generate_user_claims(user, STANDARD_CLAIMS, DEFAULT_JWT_MAPPING_CONFIG) assert "email" not in data
def test_photo_edition( testclient, slapd_server, slapd_connection, logged_user, jpeg_photo, ): # Add a photo res = testclient.get("/profile/user", status=200) res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo) res.form["jpegPhoto_delete"] = False res = res.form.submit(name="action", value="edit", status=200) assert "Profile updated successfuly." in res, str(res) with testclient.app.app_context(): logged_user = User.get(dn=logged_user.dn, conn=slapd_connection) assert [jpeg_photo] == logged_user.jpegPhoto # No change res = testclient.get("/profile/user", status=200) res.form["jpegPhoto_delete"] = False res = res.form.submit(name="action", value="edit", status=200) assert "Profile updated successfuly." in res, str(res) with testclient.app.app_context(): logged_user = User.get(dn=logged_user.dn, conn=slapd_connection) assert [jpeg_photo] == logged_user.jpegPhoto # Photo deletion res = testclient.get("/profile/user", status=200) res.form["jpegPhoto_delete"] = True res = res.form.submit(name="action", value="edit", status=200) assert "Profile updated successfuly." in res, str(res) with testclient.app.app_context(): logged_user = User.get(dn=logged_user.dn, conn=slapd_connection) assert [] == logged_user.jpegPhoto # Photo deletion AND upload, this should never happen res = testclient.get("/profile/user", status=200) res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo) res.form["jpegPhoto_delete"] = True res = res.form.submit(name="action", value="edit", status=200) assert "Profile updated successfuly." in res, str(res) with testclient.app.app_context(): logged_user = User.get(dn=logged_user.dn, conn=slapd_connection) assert [] == logged_user.jpegPhoto
def test_custom_format_claim_is_formatted_with_empty_value_and_not_omitted( testclient, slapd_connection, user): # If the jwt mapping config is customized, it's not canaille's responsability to verify value consistency when one user attribute is not set or null. # Attribute field is left empty in the formatted string. User.ldap_object_classes(slapd_connection) jwt_mapping_config = DEFAULT_JWT_MAPPING_CONFIG.copy() jwt_mapping_config["EMAIL"] = "{{ user.givenName[0] }}@mydomain.tld" with testclient.app.app_context(): data = generate_user_claims(user, STANDARD_CLAIMS, jwt_mapping_config) assert data["email"] == "@mydomain.tld"
def test_generate_user_standard_claims_with_default_config( testclient, slapd_connection, user): User.ldap_object_classes(slapd_connection) with testclient.app.app_context(): data = generate_user_claims(user, STANDARD_CLAIMS, DEFAULT_JWT_MAPPING_CONFIG) assert data == { "name": "John (johnny) Doe", "family_name": "Doe", "email": "*****@*****.**", "sub": "user", }
def test_invitation(testclient, slapd_connection, logged_admin, foo_group, smtpd): with testclient.app.app_context(): assert User.get("someone", conn=slapd_connection) is None res = testclient.get("/invite", status=200) res.form["uid"] = "someone" res.form["uid_editable"] = False res.form["mail"] = "*****@*****.**" res.form["groups"] = [foo_group.dn] res = res.form.submit(name="action", value="send", status=200) assert len(smtpd.messages) == 1 url = res.pyquery("#copy-text")[0].value # logout with testclient.session_transaction() as sess: del sess["user_dn"] res = testclient.get(url, status=200) assert res.form["uid"].value == "someone" assert res.form["uid"].attrs["readonly"] assert res.form["mail"].value == "*****@*****.**" assert res.form["groups"].value == [foo_group.dn] res.form["password1"] = "whatever" res.form["password2"] = "whatever" res.form["givenName"] = "George" res.form["sn"] = "Abitbol" res = res.form.submit(status=302) res = res.follow(status=200) assert "You account has been created successfuly." in res with testclient.app.app_context(): user = User.get("someone", conn=slapd_connection) user.load_groups(conn=slapd_connection) foo_group.reload(slapd_connection) assert user.check_password("whatever") assert user.groups == [foo_group] with testclient.session_transaction() as sess: assert "user_dn" in sess del sess["user_dn"] res = testclient.get(url, status=302)
def test_registration_no_password(testclient, slapd_connection, foo_group): with testclient.app.app_context(): invitation = Invitation( datetime.now().isoformat(), "someoneelse", False, "*****@*****.**", foo_group.dn, ) hash = invitation.profile_hash() b64 = invitation.b64() url = f"/register/{b64}/{hash}" res = testclient.get(url, status=200) assert "required" in res.form["password1"].attrs assert "required" in res.form["password2"].attrs res = res.form.submit(status=200) assert "This field is required." in res.text, res.text with testclient.app.app_context(): assert not User.get("someoneelse", conn=slapd_connection) with testclient.session_transaction() as sess: assert "user_dn" not in sess
def test_admin_self_deletion(testclient, slapd_connection): User.ldap_object_classes(slapd_connection) admin = User( objectClass=["inetOrgPerson"], cn="Temp admin", sn="admin", uid="temp", mail="*****@*****.**", userPassword="******", ) admin.save(slapd_connection) with testclient.session_transaction() as sess: sess["user_dn"] = [admin.dn] res = testclient.get("/profile/temp") res = ( res.form.submit(name="action", value="delete", status=302) .follow(status=302) .follow(status=200) ) with testclient.app.app_context(): assert User.get("temp", conn=slapd_connection) is None with testclient.session_transaction() as sess: assert not sess.get("user_dn")
def test_generate_link(testclient, slapd_connection, logged_admin, foo_group, smtpd): with testclient.app.app_context(): assert User.get("sometwo", conn=slapd_connection) is None res = testclient.get("/invite", status=200) res.form["uid"] = "sometwo" res.form["mail"] = "*****@*****.**" res.form["groups"] = [foo_group.dn] res = res.form.submit(name="action", value="generate", status=200) assert len(smtpd.messages) == 0 url = res.pyquery("#copy-text")[0].value # logout with testclient.session_transaction() as sess: del sess["user_dn"] res = testclient.get(url, status=200) assert res.form["uid"].value == "sometwo" assert res.form["mail"].value == "*****@*****.**" assert res.form["groups"].value == [foo_group.dn] res.form["password1"] = "whatever" res.form["password2"] = "whatever" res.form["givenName"] = "George" res.form["sn"] = "Abitbol" res = res.form.submit(status=302) res = res.follow(status=200) with testclient.app.app_context(): user = User.get("sometwo", conn=slapd_connection) user.load_groups(conn=slapd_connection) foo_group.reload(slapd_connection) assert user.check_password("whatever") assert user.groups == [foo_group] with testclient.session_transaction() as sess: assert "user_dn" in sess del sess["user_dn"] res = testclient.get(url, status=302)
def test_user_self_deletion(testclient, slapd_connection): User.ldap_object_classes(slapd_connection) user = User( objectClass=["inetOrgPerson"], cn="Temp user", sn="user", uid="temp", mail="*****@*****.**", userPassword="******", ) user.save(slapd_connection) with testclient.session_transaction() as sess: sess["user_dn"] = [user.dn] testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = [] res = testclient.get("/profile/temp") assert "Delete my account" not in res testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = ["delete_account"] res = testclient.get("/profile/temp") assert "Delete my account" in res res = ( res.form.submit(name="action", value="delete", status=302) .follow(status=302) .follow(status=200) ) with testclient.app.app_context(): assert User.get("temp", conn=slapd_connection) is None with testclient.session_transaction() as sess: assert not sess.get("user_dn") testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = []
def test_oauth_hybrid(testclient, slapd_connection, user, client): User.ldap_object_attributes(slapd_connection) res = testclient.get( "/oauth/authorize", params=dict( response_type="code token", client_id=client.client_id, scope="openid profile", nonce="somenonce", ), status=200, ) assert "text/html" == res.content_type, res.json res.form["login"] = user.name res.form["password"] = "******" res = res.form.submit(status=302) res = res.follow(status=200) assert "text/html" == res.content_type, res.json res = res.form.submit(name="answer", value="accept", status=302) assert res.location.startswith(client.redirect_uris[0]) params = parse_qs(urlsplit(res.location).fragment) code = params["code"][0] authcode = AuthorizationCode.get(code, conn=slapd_connection) assert authcode is not None access_token = params["access_token"][0] token = Token.get(access_token, conn=slapd_connection) assert token is not None res = testclient.get( "/oauth/userinfo", headers={"Authorization": f"Bearer {access_token}"}, status=200, ) assert { "name": "John (johnny) Doe", "family_name": "Doe", "sub": "user", "groups": [], } == res.json
def test_user_without_password_first_login(testclient, slapd_connection): User.ldap_object_classes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="Temp User", sn="Temp", uid="temp", mail="*****@*****.**", ) u.save(slapd_connection) res = testclient.get("/login", status=200) res.form["login"] = "******" res = res.form.submit(status=302).follow(status=200) assert "First login" in res u.delete(conn=slapd_connection)
def user(app, slapd_connection): User.ldap_object_classes(slapd_connection) LDAPObject.ldap_object_attributes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="John (johnny) Doe", sn="Doe", uid="user", mail="*****@*****.**", userPassword="******", ) u.save(slapd_connection) return u
def admin(app, slapd_connection): User.ldap_object_classes(slapd_connection) LDAPObject.ldap_object_attributes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="Jane Doe", sn="Doe", uid="admin", mail="*****@*****.**", userPassword="******", ) u.save(slapd_connection) return u
def moderator(app, slapd_connection): User.ldap_object_classes(slapd_connection) LDAPObject.ldap_object_attributes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="Jack Doe", sn="Doe", uid="moderator", mail="*****@*****.**", userPassword="******", ) u.save(slapd_connection) return u
def test_field_permissions_none(testclient, slapd_server, slapd_connection, logged_user): testclient.get("/profile/user", status=200) with testclient.app.app_context(): logged_user.telephoneNumber = ["555-666-777"] logged_user.save(conn=slapd_connection) testclient.app.config["ACL"]["DEFAULT"] = {"READ": ["uid"], "WRITE": []} res = testclient.get("/profile/user", status=200) assert "telephoneNumber" not in res.form.fields testclient.post("/profile/user", { "action": "edit", "telephoneNumber": "000-000-000" }) with testclient.app.app_context(): user = User.get(dn=logged_user.dn, conn=slapd_connection) assert user.telephoneNumber == ["555-666-777"]
def test_user_deleted_in_session(testclient, slapd_connection): User.ldap_object_classes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="Jake Doe", sn="Jake", uid="jake", mail="*****@*****.**", userPassword="******", ) u.save(slapd_connection) testclient.get("/profile/jake", status=403) with testclient.session_transaction() as session: session["user_dn"] = [u.dn] testclient.get("/profile/jake", status=200) u.delete(conn=slapd_connection) testclient.get("/profile/jake", status=403) with testclient.session_transaction() as session: assert not session.get("user_dn")
def current_user(): if not session.get("user_dn"): return None if not isinstance(session.get("user_dn"), list): del session["user_dn"] return None dn = session["user_dn"][-1] try: user = User.get(dn=dn) except ldap.LDAPError: return None if not user: try: session["user_dn"] = session["user_dn"][:-1] except IndexError: del session["user_dn"] return user
def test_email_reset_button(smtpd, testclient, slapd_connection, logged_admin): User.ldap_object_classes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="Temp User", sn="Temp", uid="temp", mail="*****@*****.**", userPassword=["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"], ) u.save(slapd_connection) res = testclient.get("/profile/temp", status=200) assert "If the user has forgotten his password" in res, res.text assert "Send" in res res = res.form.submit(name="action", value="password-reset-mail", status=200) assert ( "A password reset link has been sent at the user email address. It should be received within 10 minutes." in res) assert "Send again" in res assert len(smtpd.messages) == 1
def test_edition( testclient, slapd_server, slapd_connection, logged_user, admin, foo_group, bar_group, jpeg_photo, ): res = testclient.get("/profile/user", status=200) assert set(res.form["groups"].options) == { ("cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", True, "foo"), ("cn=bar,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", False, "bar"), } assert logged_user.groups == [foo_group] assert foo_group.member == [logged_user.dn] assert bar_group.member == [admin.dn] assert res.form["groups"].attrs["readonly"] assert res.form["uid"].attrs["readonly"] res.form["uid"] = "toto" res.form["givenName"] = "given_name" res.form["sn"] = "family_name" res.form["mail"] = "*****@*****.**" res.form["telephoneNumber"] = "555-666-777" res.form["employeeNumber"] = 666 res.form["groups"] = [ "cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", "cn=bar,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", ] res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo) res = res.form.submit(name="action", value="edit", status=200) assert "Profile updated successfuly." in res, str(res) with testclient.app.app_context(): logged_user = User.get(dn=logged_user.dn, conn=slapd_connection) logged_user.load_groups(conn=slapd_connection) assert ["user"] == logged_user.uid assert ["given_name"] == logged_user.givenName assert ["family_name"] == logged_user.sn assert ["*****@*****.**"] == logged_user.mail assert ["555-666-777"] == logged_user.telephoneNumber assert "666" == logged_user.employeeNumber assert [jpeg_photo] == logged_user.jpegPhoto foo_group.reload(slapd_connection) bar_group.reload(slapd_connection) assert logged_user.groups == [foo_group] assert foo_group.member == [logged_user.dn] assert bar_group.member == [admin.dn] with testclient.app.app_context(): assert logged_user.check_password("correct horse battery staple") logged_user.uid = ["user"] logged_user.cn = ["John (johnny) Doe"] logged_user.sn = ["Doe"] logged_user.mail = ["*****@*****.**"] logged_user.givenName = None logged_user.jpegPhoto = None logged_user.save(conn=slapd_connection)
def test_first_login_mail_button(smtpd, testclient, slapd_connection, logged_admin): User.ldap_object_classes(slapd_connection) u = User( objectClass=["inetOrgPerson"], cn="Temp User", sn="Temp", uid="temp", mail="*****@*****.**", ) u.save(slapd_connection) res = testclient.get("/profile/temp", status=200) assert "This user does not have a password yet" in res assert "Send" in res res = res.form.submit(name="action", value="password-initialization-mail", status=200) assert ( "A password initialization link has been sent at the user email address. It should be received within 10 minutes." in res) assert "Send again" in res assert len(smtpd.messages) == 1 u.reload(slapd_connection) u.userPassword = ["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"] u.save(slapd_connection) res = testclient.get("/profile/temp", status=200) assert "This user does not have a password yet" not in res
def validate_configuration(config): from canaille.models import User, Group try: conn = ldap.initialize(config["LDAP"]["URI"]) if config["LDAP"].get("TIMEOUT"): conn.set_option(ldap.OPT_NETWORK_TIMEOUT, config["LDAP"]["TIMEOUT"]) conn.simple_bind_s(config["LDAP"]["BIND_DN"], config["LDAP"]["BIND_PW"]) except ldap.SERVER_DOWN as exc: raise ConfigurationException( f'Could not connect to the LDAP server \'{config["LDAP"]["URI"]}\'' ) from exc except ldap.INVALID_CREDENTIALS as exc: raise ConfigurationException( f'LDAP authentication failed with user \'{config["LDAP"]["BIND_DN"]}\'' ) from exc try: User.ldap_object_classes(conn) user = User( objectClass=["inetOrgPerson"], cn=f"canaille_{uuid.uuid4()}", sn=f"canaille_{uuid.uuid4()}", uid=f"canaille_{uuid.uuid4()}", mail=f"canaille_{uuid.uuid4()}@mydomain.tld", userPassword="******", ) user.save(conn) user.delete(conn) except ldap.INSUFFICIENT_ACCESS as exc: raise ConfigurationException( f'LDAP user \'{config["LDAP"]["BIND_DN"]}\' cannot create ' f'users at \'{config["LDAP"]["USER_BASE"]}\'') from exc try: Group.ldap_object_classes(conn) user = User( objectClass=["inetOrgPerson"], cn=f"canaille_{uuid.uuid4()}", sn=f"canaille_{uuid.uuid4()}", uid=f"canaille_{uuid.uuid4()}", mail=f"canaille_{uuid.uuid4()}@mydomain.tld", userPassword="******", ) user.save(conn) group = Group( objectClass=["groupOfNames"], cn=f"canaille_{uuid.uuid4()}", member=[user.dn], ) group.save(conn) group.delete(conn) except ldap.INSUFFICIENT_ACCESS as exc: raise ConfigurationException( f'LDAP user \'{config["LDAP"]["BIND_DN"]}\' cannot create ' f'groups at \'{config["LDAP"]["GROUP_BASE"]}\'') from exc finally: user.delete(conn) conn.unbind_s()
def test_user_creation_edition_and_deletion(testclient, slapd_connection, logged_moderator, foo_group, bar_group): # The user does not exist. res = testclient.get("/users", status=200) with testclient.app.app_context(): assert User.get("george", conn=slapd_connection) is None assert "george" not in res.text # Fill the profile for a new user. res = testclient.get("/profile", status=200) res.form["uid"] = "george" res.form["givenName"] = "George" res.form["sn"] = "Abitbol" res.form["mail"] = "*****@*****.**" res.form["telephoneNumber"] = "555-666-888" res.form["groups"] = [ "cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org" ] res.form["password1"] = "totoyolo" res.form["password2"] = "totoyolo" # User have been created res = res.form.submit(name="action", value="edit", status=302).follow(status=200) with testclient.app.app_context(): george = User.get("george", conn=slapd_connection) george.load_groups(conn=slapd_connection) foo_group.reload(slapd_connection) assert "George" == george.givenName[0] assert george.groups == [foo_group] assert george.check_password("totoyolo") assert "george" in testclient.get("/users", status=200).text assert "readonly" not in res.form["groups"].attrs res.form["givenName"] = "Georgio" res.form["groups"] = [ "cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", "cn=bar,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", ] # User have been edited res = res.form.submit(name="action", value="edit", status=200) with testclient.app.app_context(): george = User.get("george", conn=slapd_connection) george.load_groups(conn=slapd_connection) assert "Georgio" == george.givenName[0] assert george.check_password("totoyolo") foo_group.reload(slapd_connection) bar_group.reload(slapd_connection) assert george.dn in set(foo_group.member) assert george.dn in set(bar_group.member) 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 # User have been deleted. res = res.form.submit(name="action", value="delete", status=302).follow(status=200) with testclient.app.app_context(): assert User.get("george", conn=slapd_connection) is None assert "george" not in res.text