def organisation_by_schac_home(): user = User.query.filter(User.id == current_user_id()).one() schac_home_organisation = user.schac_home_organisation if not schac_home_organisation: return None, 200 # Because of subdomains we need the schac_home of which the users schac_home is a strict subdomain schac_homes = SchacHomeOrganisation.query.all() hits = list(filter( lambda schac_home: schac_home_organisation == schac_home.name or schac_home_organisation.endswith( f".{schac_home.name}"), schac_homes)) if not hits: return None, 200 org = Organisation.query.get(hits[0].organisation_id) entitlement = current_app.app_config.collaboration_creation_allowed_entitlement auto_aff = bool(user.entitlement) and entitlement in user.entitlement return {"id": org.id, "name": org.name, "logo": org.logo, "collaboration_creation_allowed": org.collaboration_creation_allowed, "collaboration_creation_allowed_entitlement": auto_aff, "required_entitlement": entitlement, "user_entitlement": user.entitlement, "has_members": len(org.organisation_memberships) > 0, "on_boarding_msg": org.on_boarding_msg, "short_name": org.short_name}, 200
def _is_already_member(client_data): collaboration_id = client_data["collaborationId"] collaboration = Collaboration.query \ .filter(Collaboration.id == collaboration_id).one() user_id = current_user_id() is_member = collaboration.is_member(user_id) return is_member
def save_service(): data = current_request.get_json() validate_ip_networks(data) _token_validity_days(data) data["status"] = STATUS_ACTIVE cleanse_short_name(data, "abbreviation") # Before the JSON is cleaned in the save method administrators = data.get("administrators", []) message = data.get("message", None) res = save(Service, custom_json=data, allow_child_cascades=False, allowed_child_collections=["ip_networks"]) service = res[0] user = User.query.get(current_user_id()) for administrator in administrators: invitation = ServiceInvitation(hash=generate_token(), message=message, invitee_email=administrator, service_id=service.id, user=user, intended_role="admin", expiry_date=default_expiry_date(), created_by=user.uid) invitation = db.session.merge(invitation) mail_service_invitation({ "salutation": "Dear", "invitation": invitation, "base_url": current_app.app_config.base_url, "wiki_link": current_app.app_config.wiki_link, "recipient": administrator }, service, [administrator]) mail_platform_admins(service) service.ip_networks return res
def service_invites(): data = current_request.get_json() service_id = data["service_id"] confirm_service_admin(service_id) administrators = data.get("administrators", []) message = data.get("message", None) intended_role = "admin" service = Service.query.get(service_id) user = User.query.get(current_user_id()) for administrator in administrators: invitation = ServiceInvitation(hash=generate_token(), message=message, invitee_email=administrator, service=service, user=user, created_by=user.uid, intended_role=intended_role, expiry_date=default_expiry_date(json_dict=data)) invitation = db.session.merge(invitation) mail_service_invitation({ "salutation": "Dear", "invitation": invitation, "base_url": current_app.app_config.base_url, "wiki_link": current_app.app_config.wiki_link, "recipient": administrator }, service, [administrator]) return None, 201
def save_organisation(): confirm_write_access() data = current_request.get_json() _clear_api_keys(data) cleanse_short_name(data) data["identifier"] = str(uuid.uuid4()) administrators = data.get("administrators", []) intended_role = data.get("intended_role", "admin") message = data.get("message", None) res = save(Organisation, custom_json=data) user = User.query.get(current_user_id()) organisation = res[0] for administrator in administrators: invitation = OrganisationInvitation(hash=generate_token(), message=message, invitee_email=administrator, organisation_id=organisation.id, user_id=user.id, intended_role=intended_role, expiry_date=default_expiry_date(), created_by=user.uid) invitation = db.session.merge(invitation) mail_organisation_invitation({ "salutation": "Dear", "invitation": invitation, "base_url": current_app.app_config.base_url, "recipient": administrator }, organisation, [administrator]) mail_platform_admins(organisation) return res
def organisation_invites(): data = current_request.get_json() organisation_id = data["organisation_id"] confirm_organisation_admin(organisation_id) administrators = data.get("administrators", []) intended_role = data.get("intended_role") intended_role = "manager" if intended_role not in ["admin", "manager"] else intended_role message = data.get("message", None) organisation = Organisation.query.get(organisation_id) user = User.query.get(current_user_id()) for administrator in administrators: invitation = OrganisationInvitation(hash=generate_token(), intended_role=intended_role, message=message, invitee_email=administrator, organisation=organisation, user=user, expiry_date=default_expiry_date(json_dict=data), created_by=user.uid) invitation = db.session.merge(invitation) mail_organisation_invitation({ "salutation": "Dear", "invitation": invitation, "base_url": current_app.app_config.base_url, "recipient": administrator }, organisation, [administrator]) return None, 201
def collaboration_invites_preview(): data = current_request.get_json() message = data.get("message", None) intended_role = data.get("intended_role", "member") collaboration = Collaboration.query.get(int(data["collaboration_id"])) confirm_collaboration_admin(collaboration.id) user = User.query.get(current_user_id()) invitation = munchify({ "user": user, "collaboration": collaboration, "intended_role": intended_role, "message": message, "hash": generate_token(), "expiry_date": default_expiry_date(data) }) html = mail_collaboration_invitation({ "salutation": "Dear", "invitation": invitation, "base_url": current_app.app_config.base_url, "wiki_link": current_app.app_config.wiki_link, }, collaboration, [], preview=True) return {"html": html}, 201
def my_organisations(): user_id = current_user_id() organisations = Organisation.query \ .join(Organisation.organisation_memberships) \ .filter(OrganisationMembership.user_id == user_id) \ .all() return organisations, 200
def organisation_by_id(organisation_id): query = Organisation.query \ .options(selectinload(Organisation.organisation_memberships) .selectinload(OrganisationMembership.user)) \ .options(selectinload(Organisation.organisation_invitations) .selectinload(OrganisationInvitation.user)) \ .options(selectinload(Organisation.api_keys)) \ .options(selectinload(Organisation.services)) \ .options(selectinload(Organisation.collaboration_requests) .selectinload(CollaborationRequest.requester)) \ .options(selectinload(Organisation.collaborations)) \ .filter(Organisation.id == organisation_id) if not request_context.is_authorized_api_call: is_admin = is_application_admin() if not is_admin: user_id = current_user_id() query = query \ .join(Organisation.organisation_memberships) \ .join(OrganisationMembership.user) \ .filter(OrganisationMembership.role.in_(["admin", "manager"])) \ .filter(OrganisationMembership.user_id == user_id) organisation = query.one() return organisation, 200
def may_request_collaboration(): user = User.query.get(current_user_id()) sho = user.schac_home_organisation if not sho: return False, 200 return Organisation.query \ .join(Organisation.schac_home_organisations) \ .filter(SchacHomeOrganisation.name == sho).count() > 0, 200
def agreed_aup(): user_id = current_user_id() version = str(current_app.app_config.aup.version) if Aup.query.filter(Aup.user_id == user_id, Aup.au_version == version).count() == 0: aup = Aup(au_version=version, user_id=user_id) save(Aup, custom_json=jsonify(aup).json, allow_child_cascades=False) session["user"] = {**session["user"], **{"user_accepted_aup": True}} location = session.get("original_destination", current_app.app_config.base_url) return {"location": location}, 201
def me(): headers = current_request.headers user_id = current_user_id() impersonate_id = headers.get("X-IMPERSONATE-ID", default=None, type=int) if impersonate_id: confirm_allow_impersonation(confirm_feature_impersonation_allowed=False) return _user_activity(user_id)
def resend_service_connection_request(service_connection_request_id): service_connection_request = ServiceConnectionRequest.query.get(service_connection_request_id) service = service_connection_request.service collaboration = service_connection_request.collaboration confirm_collaboration_admin(collaboration.id) user = User.query.get(current_user_id()) _do_mail_request(collaboration, service, service_connection_request, True, user) return {}, 200
def override_func(): user_id = current_user_id() organisation_id = current_request.get_json()["id"] count = OrganisationMembership.query \ .filter(OrganisationMembership.user_id == user_id) \ .filter(OrganisationMembership.organisation_id == organisation_id) \ .filter(OrganisationMembership.role == "admin") \ .count() return count > 0
def identity_provider_display_name(): user = User.query.filter(User.id == current_user_id()).one() schac_home_organisation = user.schac_home_organisation if not schac_home_organisation: return None, 200 lang = query_param("lang", required=False, default="en") idp_name = idp_display_name(schac_home_organisation, lang) return {"display_name": idp_name}, 200
def my_organisations_lite(): user_id = current_user_id() query = Organisation.query if not is_application_admin(): query = query \ .join(Organisation.organisation_memberships) \ .filter(OrganisationMembership.user_id == user_id) \ .filter(OrganisationMembership.role.in_(["admin", "manager"])) organisations = query.all() return organisations, 200
def reset2fa(): user = User.query.filter(User.id == current_user_id()).one() data = current_request.get_json() token = data["token"] if not token or token != user.mfa_reset_token: raise Forbidden() user.second_factor_auth = None user.mfa_reset_token = None db.session.merge(user) db.session.commit() return {}, 201
def delete_organisation_membership(organisation_id, user_id): if current_user_id() != int(user_id): confirm_organisation_admin(organisation_id) memberships = OrganisationMembership.query \ .filter(OrganisationMembership.organisation_id == organisation_id) \ .filter(OrganisationMembership.user_id == user_id) \ .all() for membership in memberships: db.session.delete(membership) return (None, 204) if len(memberships) > 0 else (None, 404)
def feedback(): cfg = current_app.app_config if not cfg.feature.feedback_enabled: raise BadRequest("feedback is not enabled") data = current_request.get_json() message = data["message"] mail_conf = cfg.mail user = User.query.get(current_user_id()) mail_feedback(mail_conf.environment, message, user, [mail_conf.info_email]) return {}, 201
def get2fa(): user = User.query.filter(User.id == current_user_id()).one() secret = pyotp.random_base32() session["second_factor_auth"] = secret name = current_app.app_config.oidc.totp_token_name secret_url = pyotp.totp.TOTP(secret).provisioning_uri(user.email, name) img = qrcode.make(secret_url) buffered = BytesIO() img.save(buffered, format="PNG") img_str = base64.b64encode(buffered.getvalue()).decode() idp_name = idp_display_name(user.schac_home_organisation, "en") return {"qr_code_base64": img_str, "secret": secret, "idp_name": idp_name}, 200
def token_reset_request_post(): data = current_request.get_json() email = data["email"] user = User.query.filter(User.id == current_user_id()).one() admins = eligible_users_to_reset_token(user) if len(list(filter(lambda admin: admin["email"] == email, admins))) == 0: raise Forbidden() user.mfa_reset_token = generate_token() db.session.merge(user) db.session.commit() mail_reset_token(email, user, data["message"]) return {}, 201
def invitations_accept(): invitation = _invitation_query() \ .filter(Invitation.hash == current_request.get_json()["hash"]) \ .one() if invitation.status != "open": raise Conflict(f"The invitation has status {invitation.status}") if invitation.expiry_date and invitation.expiry_date < datetime.datetime.now(): if invitation.external_identifier: invitation.status = "expired" db.session.merge(invitation) db.session.commit() else: delete(Invitation, invitation.id) raise Conflict(f"The invitation has expired at {invitation.expiry_date}") collaboration = invitation.collaboration user_id = current_user_id() if collaboration.is_member(user_id): delete(Invitation, invitation.id) raise Conflict(f"User {user_id} is already a member of {collaboration.name}") role = invitation.intended_role if invitation.intended_role else "member" collaboration_membership = CollaborationMembership(user_id=user_id, collaboration_id=collaboration.id, role=role, expiry_date=invitation.membership_expiry_date, created_by=invitation.user.uid, updated_by=invitation.user.uid) if invitation.external_identifier: collaboration_membership.invitation_id = invitation.id collaboration_membership = db.session.merge(collaboration_membership) # We need the persistent identifier of the collaboration_membership which will be generated after the delete-commit if invitation.external_identifier: invitation.status = "accepted" db.session.merge(invitation) db.session.commit() else: delete(Invitation, invitation.id) # ensure all authorisation group membership are added groups = invitation.groups + list(filter(lambda ag: ag.auto_provision_members, collaboration.groups)) for group in set(list({ag.id: ag for ag in groups}.values())): group.collaboration_memberships.append(collaboration_membership) db.session.merge(group) add_user_aups(collaboration, user_id) res = {'collaboration_id': collaboration.id, 'user_id': user_id} return res, 201
def save_collaboration(): data = current_request.get_json() if "organisation_id" in data: confirm_organisation_admin_or_manager(data["organisation_id"]) organisation = Organisation.query.get(data["organisation_id"]) user = User.query.get(current_user_id()) else: raise BadRequest("No organisation_id in POST data") current_user_admin = data.get("current_user_admin", False) if "current_user_admin" in data: del data["current_user_admin"] res = do_save_collaboration(data, organisation, user, current_user_admin) return res
def request_service_connection(): data = current_request.get_json() service = Service.query.get(int(data["service_id"])) collaboration = Collaboration.query.get(int(data["collaboration_id"])) confirm_collaboration_member(collaboration.id) user = User.query.get(current_user_id()) is_admin = collaboration.is_admin(user.id) request_new_service_connection(collaboration, data.get("message"), is_admin, service, user) return {}, 201
def verify2fa(): user = User.query.filter(User.id == current_user_id()).one() secret = user.second_factor_auth if user.second_factor_auth else session["second_factor_auth"] valid_totp = _do_verify_2fa(user, secret) if valid_totp: location = session.get("original_destination", current_app.app_config.base_url) in_proxy_flow = session.get("in_proxy_flow", False) if in_proxy_flow: oidc_config = current_app.app_config.oidc id_token = _construct_jwt(user, str(uuid4()), oidc_config) location = f"{oidc_config.sfo_eduteams_redirect_uri}?id_token={id_token}" return {"location": location, "in_proxy_flow": in_proxy_flow}, 201 else: return {"new_totp": False}, 400
def my_collaborations_lite(): include_services = query_param("includeServices", False) user_id = current_user_id() query = Collaboration.query \ .join(Collaboration.collaboration_memberships) \ .options(selectinload(Collaboration.organisation)) if include_services: query = query \ .options(selectinload(Collaboration.services).selectinload(Service.allowed_organisations)) collaborations = query \ .filter(CollaborationMembership.user_id == user_id) \ .all() return collaborations, 200
def create_collaboration_membership_role(): client_data = current_request.get_json() collaboration_id = client_data["collaborationId"] collaboration = Collaboration.query.get(collaboration_id) confirm_organisation_admin_or_manager(collaboration.organisation_id) user_id = current_user_id() collaboration_membership = CollaborationMembership(user_id=user_id, collaboration_id=collaboration.id, role="admin", created_by=current_user_uid(), updated_by=current_user_uid()) collaboration_membership = db.session.merge(collaboration_membership) return collaboration_membership, 201
def _sanitize_and_verify(hash_token=True): data = current_request.get_json() if data["user_id"] != current_user_id(): raise Forbidden("Can not create user_token for someone else") if hash_token: data = hash_secret_key(data, "hashed_token") service_id = data["service_id"] service = Service.query.get(service_id) if not user_service(service_id): raise Forbidden(f"User has no access to {service.name}") if not service.token_enabled: raise Forbidden(f"Can not create user_token for service {service.name}") return data
def update2fa(): user = User.query.filter(User.id == current_user_id()).one() current_secret = user.second_factor_auth new_secret = session["second_factor_auth"] data = current_request.get_json() current_totp_value = data["current_totp"] new_totp_value = data["new_totp_value"] verified_current = pyotp.TOTP(current_secret).verify(current_totp_value) verified_new = pyotp.TOTP(new_secret).verify(new_totp_value) if not verified_current or not verified_new: return {"current_totp": not verified_current, "new_totp": not verified_new}, 400 user.second_factor_auth = new_secret db.session.merge(user) db.session.commit() return {}, 201
def delete_user(): user_id = current_user_id() user = User.query.get(user_id) mail_account_deletion(user) db.session.delete(user) if user.username: history_not_exists = UserNameHistory.query.filter(UserNameHistory.username == user.username).count() == 0 if history_not_exists: user_name_history = UserNameHistory(username=user.username) db.session.merge(user_name_history) db.session.commit() session["user"] = None session.clear() return user, 204