def test_conflict_usernames_same_org(self, mock_send_mail): with app.test_client() as c: c.set_cookie("localhost", "access_token_cookie", TOKEN_ADMIN) username = get_random_username() email = get_random_email() response = c.post( "/organizations/%s/users" % UUID_ORG, headers={"x-csrf-token": TOKEN_ADMIN_CSRF}, json={ "username": username, "role": "user", "email": email }, ) self.assertEqual(response.status_code, 201) user = users.get_by_email(email) self.assertEqual(mock_send_mail.call_count, 1) response = c.post( "/organizations/%s/users" % UUID_ORG, headers={"x-csrf-token": TOKEN_ADMIN_CSRF}, json={ "username": username, "role": "user", "email": email }, ) self.assertEqual(response.status_code, 201) user2 = users.get_by_email(email) self.assertTrue( user["activation_key_hash"] != user2["activation_key_hash"]) self.assertEqual(mock_send_mail.call_count, 2)
def test_reissue_activation_key(self, mock_send_mail): with app.test_client() as c: email = get_random_email() response = c.post("/users/me", json={"email": email}) self.assertEqual(response.status_code, 202) user = users.get_by_email(email) self.assertTrue(user["activation_key_hash"] is not None) response = c.post("/users/me", json={"email": email}) self.assertEqual(response.status_code, 202) user2 = users.get_by_email(email) self.assertTrue( user["activation_key_hash"] != user2["activation_key_hash"]) self.assertEqual(mock_send_mail.call_count, 2)
def request_password_reset(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", "").lstrip().rstrip().lower() if not email or not is_email(email): return abort( make_response(jsonify(message="Missing or bad email"), 400)) existing = users.get_by_email(email) # we are not leaking used or unused e-mails, that's why this responds 202 if not existing or existing["activated"] is None: return "", 202 local = local_users.get_local_user(existing["uuid"]) if not local: return abort( make_response(jsonify(message="User does not have a password"), 400)) pwd_reset_key = guid.uuid4() pwd_reset_key_expires = datetime.now().astimezone() + timedelta(hours=1) local_users.update_local_user( user_uuid=existing["uuid"], pwd_reset_key_hash=hashlib.sha256( str(pwd_reset_key).encode("utf-8")).hexdigest(), pwd_reset_key_expires=pwd_reset_key_expires, ) send_mail.delay( [email], "PASSWORD_RESET_LINK", { "pwd_reset_key": pwd_reset_key, "pwd_reset_key_expires": int(pwd_reset_key_expires.timestamp()), "email": email, }, ) return "", 202
def test_create_user(self, mock_send_mail): with app.test_client() as c: email = get_random_email() c.set_cookie("localhost", "access_token_cookie", TOKEN_ADMIN) response = c.post( "/organizations/%s/users" % UUID_ORG, headers={"x-csrf-token": TOKEN_ADMIN_CSRF}, json={"role": "user", "email": " %s " % email.upper()}, ) self.assertEqual(response.status_code, 201) self.assertTrue("uuid" in response.json) self.assertTrue("username" in response.json) self.assertTrue("role" in response.json) self.assertTrue("email" in response.json) user = users.get_by_email(email) self.assertTrue(user is not None) self.assertEqual(user["username"], email) self.assertEqual(user["email"], email) self.assertEqual(user["system_role"], "user") luser = local_users.get_local_user(user["uuid"]) self.assertTrue(luser is None) args = mock_send_mail.call_args_list self.assertEqual(args[0][0][0][0], email) self.assertEqual(args[0][0][1], "REGISTRATION_VERIFICATION_EMAIL") self.assertTrue(args[0][0][2]["activation_key"] is not None) self.assertTrue(args[0][0][2]["activation_key_expires"] is not None) self.assertTrue(args[0][0][2]["organization_name"] is not None) self.assertTrue(args[0][0][2]["organization_uuid"] is not None) self.assertEqual(args[0][0][2]["email"], email) orgrole = organization_roles.get_organization_role(UUID_ORG, user["uuid"]) self.assertTrue(orgrole["role"], "user")
def activate_user(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", "").lstrip().rstrip().lower() activation_key = data.get("activation_key", None) password = data.get("password", None) password_confirmation = data.get("password_confirmation", None) if not email or not is_email(email): return abort( make_response(jsonify(message="Missing or bad email"), 400)) if not password: return abort(make_response(jsonify(message="Missing password"), 400)) if not activation_key: return abort( make_response(jsonify(message="Missing activation_key"), 400)) if password != password_confirmation: return abort( make_response(jsonify(message="Passwords do not match"), 400)) existing = users.get_by_email(email) if not existing: return abort(make_response(jsonify(message="Cannot activate"), 400)) if existing["activated"] is not None: return abort(make_response(jsonify(message="Already activated"), 409)) if (hashlib.sha256(str(activation_key).encode("utf-8")).hexdigest() != existing["activation_key_hash"]): return abort(make_response(jsonify(message="Cannot activate"), 400)) if existing["activation_key_expires"] < datetime.now().astimezone(): return abort( make_response(jsonify(message="Activation key expired"), 400)) memberships = organization_roles.get_by_user_uuid(existing["uuid"]) if len(memberships) == 0: orguuid = guid.uuid4() organizations.add_organization(uuid=orguuid, name="Default organization") organization_roles.set_organization_role(orguuid, existing["uuid"], "admin") pwd_hash = bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt()) users.update_user( uuid=existing["uuid"], activated=datetime.now().astimezone(), providers=["local"], providers_data={}, ) local_users.add_local_user( user_uuid=existing["uuid"], pwd_hash=pwd_hash.decode("utf8"), force_pwd_change=False, ) return "", 204
def test_existing_user_different_org(self, mock_send_mail): with app.test_client() as c: c.set_cookie("localhost", "access_token_cookie", TOKEN_ADMIN) username = get_random_username() email = get_random_email() response = c.post( "/organizations/%s/users" % UUID_ORG, headers={"x-csrf-token": TOKEN_ADMIN_CSRF}, json={ "username": username, "role": "user", "email": email }, ) self.assertEqual(response.status_code, 201) user = users.get_by_email(email) args = mock_send_mail.call_args_list self.assertEqual(args[0][0][0][0], email) self.assertEqual(args[0][0][1], "REGISTRATION_VERIFICATION_EMAIL") self.assertTrue(args[0][0][2]["activation_key"] is not None) self.assertTrue( args[0][0][2]["activation_key_expires"] is not None) self.assertTrue(args[0][0][2]["organization_name"] is not None) self.assertTrue(args[0][0][2]["organization_uuid"] == UUID_ORG) self.assertEqual(args[0][0][2]["email"], email) orgrole = organization_roles.get_organization_role( UUID_ORG, user["uuid"]) self.assertTrue(orgrole["role"], "user") # fake activate users.update_user(uuid=user["uuid"], activated=datetime.now()) c.set_cookie("localhost", "access_token_cookie", TOKEN_USER) response = c.post( "/organizations/%s/users" % UUID_ORG2, headers={"x-csrf-token": TOKEN_USER_CSRF}, json={ "username": username, "role": "user", "email": email }, ) self.assertEqual(response.status_code, 201) args = mock_send_mail.call_args_list self.assertEqual(args[1][0][0][0], email) self.assertEqual(args[1][0][1], "ORGANIZATION_INVITATION") self.assertTrue("activation_key" not in args[1][0][2]) self.assertTrue("activation_key_expires" not in args[1][0][2]) self.assertTrue(args[1][0][2]["organization_name"] is not None) self.assertTrue(args[1][0][2]["organization_uuid"] == UUID_ORG2) self.assertEqual(args[1][0][2]["email"], email) orgrole = organization_roles.get_organization_role( UUID_ORG2, user["uuid"]) self.assertTrue(orgrole["role"], "user")
def reset_password(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", "").lstrip().rstrip().lower() pwd_reset_key = data.get("pwd_reset_key", None) password = data.get("password", None) password_confirmation = data.get("password_confirmation", None) try: validate_email(email, check_deliverability=False) except EmailNotValidError as e: return abort( make_response(jsonify(message=f"Email not valid: {e}"), 400)) if not password: return abort(make_response(jsonify(message="Missing password"), 400)) if not pwd_reset_key: return abort(make_response(jsonify(message="Missing reset key"), 400)) if password != password_confirmation: return abort(make_response(jsonify(message="Password mismatch"), 400)) existing = users.get_by_email(email) if not existing: return abort(make_response(jsonify(message="Cannot reset"), 400)) local = local_users.get_local_user(existing["uuid"]) if not local: return abort( make_response(jsonify(message="User does not have a password"), 400)) if (hashlib.sha256(str(pwd_reset_key).encode("utf-8")).hexdigest() != local["pwd_reset_key_hash"]): return abort(make_response(jsonify(message="Cannot reset"), 400)) if local["pwd_reset_key_expires"] < datetime.now().astimezone(): return abort(make_response(jsonify(message="Reset key expired"), 400)) pwd_hash = bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt()) local_users.update_local_user( user_uuid=existing["uuid"], pwd_hash=pwd_hash.decode("utf8"), pwd_reset_key_hash=None, pwd_reset_key_expires=None, force_pwd_change=False, ) send_mail.delay( [email], "PASSWORD_RESET_CONFIRMATION", { "email": email, }, ) return "", 204
def activate_user(email, activation_key, password, password_confirmation): try: validate_email(email, check_deliverability=False) except EmailNotValidError as e: return {"activated": False, "message": f"Email not valid: {e}"} if not password: return {"activated": False, "message": "Missing password"} if not activation_key: return {"activated": False, "message": "Missing activation_key"} if password != password_confirmation: return {"activated": False, "message": "Passwords do not match"} existing = users.get_by_email(email) if not existing: return {"activated": False, "message": "Cannot activate"} if existing["activated"] is not None: return { "activated": False, "already_activated": True, "message": "Already activated", } if (hashlib.sha256(str(activation_key).encode("utf-8")).hexdigest() != existing["activation_key_hash"]): return {"activated": False, "message": "Cannot activate"} if existing["activation_key_expires"] < datetime.now().astimezone(): return {"activated": False, "message": "Activation key expired"} memberships = organization_roles.get_by_user_uuid(existing["uuid"]) if len(memberships) == 0: orguuid = guid.uuid4() organizations.add_organization(uuid=orguuid, name="Default organization") organization_roles.set_organization_role(orguuid, existing["uuid"], "admin") pwd_hash = bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt()) users.update_user( uuid=existing["uuid"], activated=datetime.now().astimezone(), providers=["local"], providers_data={}, ) local_users.add_local_user( user_uuid=existing["uuid"], pwd_hash=pwd_hash.decode("utf8"), force_pwd_change=False, ) return {"activated": True, "user_uuid": existing["uuid"]}
def create_inactive_user(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", "").lstrip().rstrip().lower() if not email or not is_email(email): return abort( make_response(jsonify(message="Missing or bad email"), 400)) existing = users.get_by_email(email) # activated users if existing and existing["activated"] is not None: return "", 202 activation_key = guid.uuid4() activation_key_expires = datetime.now().astimezone() + timedelta( minutes=10) # reissue activation_key if existing: users.update_user( uuid=existing["uuid"], activation_key_hash=hashlib.sha256( str(activation_key).encode("utf-8")).hexdigest(), activation_key_expires=activation_key_expires, ) # completely new user else: users.add_user( uuid=guid.uuid4(), username=email, email=email, system_role="user", providers=[], activation_key_hash=hashlib.sha256( str(activation_key).encode("utf-8")).hexdigest(), activation_key_expires=activation_key_expires, ) send_mail.delay( [email], "REGISTRATION_VERIFICATION_EMAIL", { "activation_key": activation_key, "activation_key_expires": int(activation_key_expires.timestamp()), "email": email, }, ) return "", 202
def test_create_inactive_user(self): with app.test_client() as c: email = get_random_email() # this is testing whitespace truncate and casing as well response = c.post("/users/me", json={"email": " %s " % email.upper()}) user = users.get_by_email(email) self.assertTrue(user is not None) self.assertEqual(user["providers"], []) self.assertEqual(user["email"], email) self.assertEqual(user["username"], email) self.assertEqual(user["activated"], None) self.assertTrue(user["activation_key_hash"] is not None) self.assertTrue(user["activation_key_expires"] is not None) self.assertTrue( user["activation_key_expires"] < (datetime.now().astimezone() + timedelta(minutes=15))) self.assertEqual(response.status_code, 202)
def test_send_email_with_reset_link(self, mock_send_mail): with app.test_client() as c: email = get_random_email() c.post("/users/me", json={"email": email}) user = users.get_by_email(email) users.update_user(uuid=user["uuid"], activated=datetime.now()) local_users.add_local_user(user_uuid=user["uuid"], pwd_hash="aaa", force_pwd_change=False) response = c.post("/users/me/request-password-reset", json={"email": email}) self.assertEqual(response.status_code, 202) self.assertEqual(mock_send_mail.call_count, 2) args = mock_send_mail.call_args_list self.assertEqual(args[1][0][0][0], email) self.assertEqual(args[1][0][1], "PASSWORD_RESET_LINK") self.assertTrue(args[1][0][2]["pwd_reset_key"] is not None) self.assertTrue(args[1][0][2]["pwd_reset_key_expires"] is not None) self.assertEqual(args[1][0][2]["email"], email)
def authenticate_base(include_refresh_token): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) username = data.get("username", None) password = data.get("password", None) if not username or not password: raise BadRequest("Missing username or password in request body.") user = users.get_by_email(username.lstrip().rstrip().lower()) # fallback to legacy username login if not user: user = users.get_by_username(username) if not user: raise Unauthorized("Invalid credentials.") if user["suspended"]: raise Unauthorized("Account suspended") local = local_users.get_local_user(user["uuid"]) if not local: raise Unauthorized("Invalid credentials.") if not bcrypt.checkpw(password.encode("utf8"), local["pwd_hash"].encode("utf8")): raise Unauthorized("Invalid credentials.") # drop reset pwd key if any if local["pwd_reset_key_hash"]: local_users.update_local_user(uuid=user["uuid"], pwd_reset_key_hash=None, pwd_reset_key_expires=None) userdata = dict(user) userdata.update(local) response = jsonify(get_user_identity(userdata, True)) access_token = create_access_token( identity=userdata, fresh=True, expires_delta=ACCESS_TOKEN_EXPIRES_AFTER) set_access_cookies(response, access_token) if include_refresh_token: refresh_token = create_refresh_token( identity=userdata, expires_delta=REFRESH_TOKEN_EXPIRES_AFTER) set_refresh_cookies(response, refresh_token, 7 * 24 * 60 * 60) return response, 200
def test_delete(self, mock_send_mail): with app.test_client() as c: c.set_cookie("localhost", "access_token_cookie", TOKEN_ADMIN) email = get_random_email() response = c.post( "/organizations/%s/users" % UUID_ORG, headers={"x-csrf-token": TOKEN_ADMIN_CSRF}, json={ "role": "user", "email": email, }, ) self.assertEqual(response.status_code, 201) uuid = response.json["uuid"] user = users.get_by_email(email) api_tokens.add_token( user_uuid=uuid, jti=guid.uuid4(), organization_uuid=UUID_ORG, name="jti", ) self.assertTrue(user["uuid"], uuid) response = c.delete( "/organizations/%s/users/%s" % (UUID_ORG, uuid), headers={"x-csrf-token": TOKEN_ADMIN_CSRF}, ) self.assertEqual(response.status_code, 204) orgrole = organization_roles.get_organization_role( UUID_ORG, user["uuid"]) tokens = api_tokens.get_tokens_for_user_uuid(user["uuid"], org_uuid=UUID_ORG) self.assertTrue(user is not None) self.assertTrue(orgrole is None) for t in tokens: self.assertTrue(t["revoked"]) self.assertTrue(t["organization_uuid"] == UUID_ORG) args = mock_send_mail.call_args_list self.assertEqual(args[1][0][0][0], email) self.assertEqual(args[1][0][1], "ORGANIZATION_REMOVAL") self.assertTrue(args[1][0][2]["organization_name"] is not None) self.assertTrue(args[1][0][2]["organization_uuid"] is not None) self.assertEqual(args[1][0][2]["email"], email)
def create_inactive_user(email): existing = users.get_by_email(email) # activated users if existing and existing["activated"] is not None: return {"created": False, "user_uuid": existing["uuid"]} activation_key = guid.uuid4() activation_key_expires = datetime.now().astimezone() + timedelta( minutes=10) user_uuid = guid.uuid4() # reissue activation_key if existing: users.update_user( uuid=existing["uuid"], activation_key_hash=hashlib.sha256( str(activation_key).encode("utf-8")).hexdigest(), activation_key_expires=activation_key_expires, ) user_uuid = existing["uuid"] # completely new user else: users.add_user( uuid=user_uuid, username=email, email=email, system_role="user", providers=[], activation_key_hash=hashlib.sha256( str(activation_key).encode("utf-8")).hexdigest(), activation_key_expires=activation_key_expires, ) return { "created": True, "activation_key": str(activation_key), "activation_key_expires": activation_key_expires, "email": email, "user_uuid": user_uuid, }
def add_user_to_org(org_uuid): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", None) org_role = data.get("role", None) if not email or not org_role: return abort(make_response(jsonify(message="Missing email"), 400)) if org_role not in ["admin", "user"]: return abort(make_response(jsonify(message="Bad role"), 400)) email = email.lstrip().rstrip().lower() if not is_email(email): return abort(make_response(jsonify(message="Invalid email"), 400)) existing = users.get_by_email(email) # completely new user if existing is None: user_uuid = guid.uuid4() activation_key = guid.uuid4() activation_key_expires = datetime.now().astimezone() + timedelta(hours=24) users.add_user( uuid=user_uuid, email=email, username=email, system_role="user", providers=[], activation_key_hash=hashlib.sha256( str(activation_key).encode("utf-8") ).hexdigest(), activation_key_expires=activation_key_expires, ) else: user_uuid = existing.get("uuid", None) # an activated account that already has a role in this organization if ( organization_roles.get_organization_role(org_uuid, user_uuid) and existing["activated"] ): return abort(make_response(jsonify(message="User already exists"), 409)) # an account that is not activated but has already been sent an invite activation_key = guid.uuid4() activation_key_expires = datetime.now().astimezone() + timedelta(hours=24) users.update_user( uuid=user_uuid, activation_key_hash=hashlib.sha256( str(activation_key).encode("utf-8") ).hexdigest(), activation_key_expires=activation_key_expires, ) organization = organizations.get_by_uuid(org_uuid) organization_roles.set_organization_role(org_uuid, user_uuid, org_role) if existing is None or existing["activated"] is None: send_mail.delay( [email], "REGISTRATION_VERIFICATION_EMAIL", { "activation_key": activation_key, "activation_key_expires": int(activation_key_expires.timestamp()), "email": email, "inviter_username": get_current_user()["username"], "organization_name": organization["name"], "organization_uuid": organization["uuid"], }, ) else: send_mail.delay( [email], "ORGANIZATION_INVITATION", { "email": email, "inviter_username": get_current_user()["username"], "organization_name": organization["name"], "organization_uuid": organization["uuid"], }, ) return ( jsonify( { "uuid": str(user_uuid), "username": existing["username"] if existing else email, "email": email, "role": org_role, "activated": existing["activated"] is not None if existing else False, } ), 201, )