From a0ddbb325cacc5ee7c16e1e3a74952a1c78f6ee7 Mon Sep 17 00:00:00 2001
From: kaiyou <pierre@jaury.eu>
Date: Mon, 4 Nov 2019 22:16:27 +0100
Subject: [PATCH] Get back to simple profile management

---
 hiboo/__init__.py                             |  4 +-
 hiboo/account/templates/account_home.html     |  2 +-
 hiboo/identity/__init__.py                    | 23 --------
 hiboo/identity/avatars.py                     | 57 -------------------
 hiboo/identity/templates/avatar_create.html   | 21 -------
 hiboo/identity/templates/avatar_pick.html     | 14 -----
 hiboo/models.py                               | 20 +++----
 hiboo/profile/__init__.py                     | 21 +++++++
 hiboo/{identity => profile}/forms.py          |  8 +--
 .../profiles.py => profile/login.py}          | 54 +++++++++---------
 .../templates/profile_claim.html              |  0
 .../templates/profile_create.html             |  0
 .../templates/profile_list.html               |  4 +-
 .../templates/profile_pick.html               |  8 +--
 hiboo/service/forms.py                        |  3 +-
 hiboo/service/templates/service_details.html  |  2 +-
 hiboo/sso/oidc.py                             |  8 +--
 hiboo/sso/saml.py                             | 10 ++--
 18 files changed, 80 insertions(+), 179 deletions(-)
 delete mode 100644 hiboo/identity/__init__.py
 delete mode 100644 hiboo/identity/avatars.py
 delete mode 100644 hiboo/identity/templates/avatar_create.html
 delete mode 100644 hiboo/identity/templates/avatar_pick.html
 create mode 100644 hiboo/profile/__init__.py
 rename hiboo/{identity => profile}/forms.py (75%)
 rename hiboo/{identity/profiles.py => profile/login.py} (70%)
 rename hiboo/{identity => profile}/templates/profile_claim.html (100%)
 rename hiboo/{identity => profile}/templates/profile_create.html (100%)
 rename hiboo/{identity => profile}/templates/profile_list.html (83%)
 rename hiboo/{identity => profile}/templates/profile_pick.html (90%)

diff --git a/hiboo/__init__.py b/hiboo/__init__.py
index 470c7c6b..9e07b699 100644
--- a/hiboo/__init__.py
+++ b/hiboo/__init__.py
@@ -30,10 +30,10 @@ def create_app_from_config(config):
         return dict(config=app.config, utils=utils)
 
     # Import views
-    from hiboo import account, user, identity, service, sso
+    from hiboo import account, user, profile, service, sso
     app.register_blueprint(account.blueprint, url_prefix='/account')
     app.register_blueprint(user.blueprint, url_prefix='/user')
-    app.register_blueprint(identity.blueprint, url_prefix='/identity')
+    app.register_blueprint(profile.blueprint, url_prefix='/profile')
     app.register_blueprint(service.blueprint, url_prefix='/service')
     app.register_blueprint(sso.blueprint, url_prefix='/sso')
 
diff --git a/hiboo/account/templates/account_home.html b/hiboo/account/templates/account_home.html
index 99223ac5..9efd300c 100644
--- a/hiboo/account/templates/account_home.html
+++ b/hiboo/account/templates/account_home.html
@@ -12,7 +12,7 @@
           {{ macros.infobox("Account age", "{} days".format((current_user.created_at.today() - current_user.created_at).total_seconds() // 86400), "aqua", "calendar") }}
         </div>
         <div class="col-md-6 col-xs-12">
-          {{ macros.infobox("Identity count", current_user.identities.__len__(), "red", "users") }}
+          {{ macros.infobox("Profile count", current_user.identities.__len__(), "red", "users") }}
         </div>
       </div>
       <div class="row">
diff --git a/hiboo/identity/__init__.py b/hiboo/identity/__init__.py
deleted file mode 100644
index ec4054a4..00000000
--- a/hiboo/identity/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import flask
-
-
-blueprint = flask.Blueprint("identity", __name__, template_folder="templates")
-
-import flask_login
-from hiboo import models, utils
-from hiboo.identity import profiles, avatars, forms
-
-
-def get_identity(service, **redirect_args):
-    form = forms.IdentityPickForm()
-    if form.validate_on_submit():
-        identity = models.Identity.query.get(form.identity_uuid.data)
-        if not (identity and
-                identity.user == flask_login.current_user and
-                identity.service == service and
-                identity.status == models.Identity.ACTIVE):
-            return None
-        return identity
-    next = ("identity.pick_avatar" if service.max_profiles == 0
-            else "identity.pick_profile")
-    utils.force_redirect(utils.url_for(next, **redirect_args))
diff --git a/hiboo/identity/avatars.py b/hiboo/identity/avatars.py
deleted file mode 100644
index a4061508..00000000
--- a/hiboo/identity/avatars.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from hiboo.identity import blueprint, forms
-from hiboo import models, utils, security
-
-import flask
-import flask_login
-
-
-@blueprint.route("/avatar/pick/<service_uuid>")
-@security.authentication_required()
-def pick_avatar(service_uuid):
-    service = models.Service.query.get(service_uuid) or flask.abort(404)
-    service.max_profiles == 0 or flask.abort(404)
-    avatar = models.Identity.filter(service, flask_login.current_user).first()
-    form = forms.IdentityPickForm()
-    if avatar:
-        if avatar.status == models.Identity.REQUEST:
-            flask.flash("Your account request is awaiting approval", "warning")
-            return flask.redirect(flask.url_for("account.home"))
-        elif avatar.status == models.Identity.BLOCKED:
-            flask.flash("You are currently blocked", "danger")
-            return flask.redirect(flask.url_for("account.home"))
-        elif avatar.status in (models.Identity.DELETED, models.Identity.UNCLAIMED):
-            flask.flash("Your avatar is unavailable", "danger")
-            return flask.redirect(fllask.url_for("account.home"))
-    elif service.policy not in (models.Service.OPEN, models.Service.MANAGED):
-        flask.flash("You cannot access this service", "danger")
-        return flask.redirect(flask.url_for("account.home"))
-    else:
-        return flask.redirect(utils.url_for("identity.create_avatar", intent=True))
-    return flask.render_template("avatar_pick.html", service=service, avatar=avatar, form=form)
-
-
-@blueprint.route("/avatar/create/<service_uuid>", methods=["GET", "POST"])
-@security.authentication_required()
-def create_avatar(service_uuid):
-    service = models.Service.query.get(service_uuid) or flask.abort(404)
-    service.max_profiles == 0 or flask.abort(404)
-    # Cannot create an avatar if one exists already
-    models.Identity.filter(service, flask_login.current_user).first() and flask.abort(403)
-    # Cannot create an avatar for anything but a managed or open service
-    if service.policy == models.Service.OPEN:
-        status = models.Identity.ACTIVE
-    elif service.policy == models.Service.MANAGED:
-        status = models.Identity.REQUEST
-    else:
-        flask.abort(403)
-    form = forms.AvatarForm()
-    if form.validate_on_submit():
-        avatar = models.Identity()
-        avatar.username = flask_login.current_user.username
-        avatar.user = flask_login.current_user
-        avatar.service = service
-        avatar.status = models.Identity.ACTIVE
-        models.db.session.add(avatar)
-        models.db.session.commit()
-        return flask.redirect(utils.url_or_intent("account.home"))
-    return flask.render_template("avatar_create.html", form=form, service=service)
diff --git a/hiboo/identity/templates/avatar_create.html b/hiboo/identity/templates/avatar_create.html
deleted file mode 100644
index 8ad2f71d..00000000
--- a/hiboo/identity/templates/avatar_create.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% extends "base.html" %}
-
-{% set service_name = service.name %}
-{% block title %}{% trans %}Sign-up{% endtrans %}{% endblock %}
-{% block subtitle %}{% trans service_name %}for the service {{ service_name }}{% endtrans %}{% endblock %}
-
-{% block content %}
-<p>{% trans service_name %}Your are about to sign up for {{ service_name }}{% endtrans %}.</p>
-{{ macros.form(form) }}
-
-{% call macros.help(_("The service that does not support profiles"), auto=utils.display_help("main")) %}
-<p>{% trans %}The service does not support profiles as alternate identities. You will thus simply need to signup.{% endtrans %}</p>
-<p>{% trans %}The service will be able to access your current Hiboo username.{% endtrans %}</p>
-<p>{% trans %}Depending on your settings, the service might also be able to send you notifications.
-  Hiboo will not directly communicate your email address or any notification destination to the service,
-  but opaque identifiers instead. Thus, you can set your notifications preferences at anytime
-  directly in Hiboo settings.{% endtrans %}
-</p>
-{% endcall %}
-
-{% endblock %}
diff --git a/hiboo/identity/templates/avatar_pick.html b/hiboo/identity/templates/avatar_pick.html
deleted file mode 100644
index b9428e83..00000000
--- a/hiboo/identity/templates/avatar_pick.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "base.html" %}
-
-{% set service_name = service.name %}
-{% block title %}{% trans %}Sign-in{% endtrans %}{% endblock %}
-{% block subtitle %}{% trans service_name %}for the service {{ service_name }}{% endtrans %}{% endblock %}
-
-{% block content %}
-<p>{% trans service_name %}Your are about to sign in for {{ service_name }}{% endtrans %}.</p>
-<form method="POST" action="{{ utils.url_or_intent("account.home") }}" class="form">
-    {{ form.hidden_tag() }}
-    <input type="hidden" name="identity_uuid" value="{{ avatar.uuid }}">
-    <input type="submit" value="Sign in as {{ current_user.username }}" class="btn btn-lg btn-flat bg-gray text-black">
-</form>
-{% endblock %}
diff --git a/hiboo/models.py b/hiboo/models.py
index 0a6481dc..f4b7b625 100644
--- a/hiboo/models.py
+++ b/hiboo/models.py
@@ -10,7 +10,7 @@ import json
 import uuid
 
 
-def log(category, value=None, comment=None, user=None, identity=None, service=None, actor=None):
+def log(category, value=None, comment=None, user=None, profile=None, service=None, actor=None):
     """ Log a history event
     """
     event = History()
@@ -18,7 +18,7 @@ def log(category, value=None, comment=None, user=None, identity=None, service=No
     event.value = value
     event.comment = comment
     event.user = user
-    event.identity = identity
+    event.profile = profile
     event.service = service
     event.actor = actor
     db.session.add(event)
@@ -161,14 +161,14 @@ class Service(db.Model):
     description = db.Column(db.String())
     policy = db.Column(db.String(255))
     max_profiles = db.Column(db.Integer(), nullable=False, default=1)
+    same_username = db.Column(db.Boolean(), nullable=False, default=False)
     config = db.Column(JSONEncoded)
 
 
-class Identity(db.Model):
-    """ An identity is either a profile or an avatar for a user and given
-    service.
+class Profile(db.Model):
+    """ A profile is a per-service custom identity.
     """
-    __tablename__ = "identity"
+    __tablename__ = "profile"
 
     UNCLAIMED = "unclaimed"
     REQUEST = "request"
@@ -200,7 +200,7 @@ class Identity(db.Model):
 
 
 class History(db.Model):
-    """ Records an even in an account's or identity's lifetime.
+    """ Records an even in an account's or profile's lifetime.
     """
     __tablename__ = "history"
 
@@ -213,17 +213,17 @@ class History(db.Model):
 
     DESCRIPTION = {
         SIGNUP: _("signed up for this account"),
-        CREATE: _("created the identity {this.identity.username} on {this.service.name}"),
+        CREATE: _("created the profile {this.profile.username} on {this.service.name}"),
         PASSWORD: _("changed your password")
     }
 
     user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
-    identity_uuid = db.Column(db.String(36), db.ForeignKey(Identity.uuid))
+    profile_uuid = db.Column(db.String(36), db.ForeignKey(Profile.uuid))
     service_uuid = db.Column(db.String(36), db.ForeignKey(Service.uuid))
     actor_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
     user = db.relationship(User, foreign_keys=[user_uuid],
         backref=db.backref('history', cascade='all, delete-orphan'))
-    identity = db.relationship(Identity,
+    profile = db.relationship(Profile,
         backref=db.backref('history', cascade='all, delete-orphan'))
     service = db.relationship(Service,
         backref=db.backref('history', cascade='all, delete-orphan'))
diff --git a/hiboo/profile/__init__.py b/hiboo/profile/__init__.py
new file mode 100644
index 00000000..9941892e
--- /dev/null
+++ b/hiboo/profile/__init__.py
@@ -0,0 +1,21 @@
+import flask
+
+
+blueprint = flask.Blueprint("profile", __name__, template_folder="templates")
+
+import flask_login
+from hiboo import models, utils
+from hiboo.profile import login, forms
+
+
+def get_profile(service, **redirect_args):
+    form = forms.ProfilePickForm()
+    if form.validate_on_submit():
+        profile = models.Profile.query.get(form.profile_uuid.data)
+        if not (profile and
+                profile.user == flask_login.current_user and
+                profile.service == service and
+                profile.status == models.Profile.ACTIVE):
+            return None
+        return profile
+    utils.force_redirect(utils.url_for("profile.pick", **redirect_args))
diff --git a/hiboo/identity/forms.py b/hiboo/profile/forms.py
similarity index 75%
rename from hiboo/identity/forms.py
rename to hiboo/profile/forms.py
index 26963bc3..51c4721e 100644
--- a/hiboo/identity/forms.py
+++ b/hiboo/profile/forms.py
@@ -10,12 +10,8 @@ class ProfileForm(flask_wtf.FlaskForm):
     submit = fields.SubmitField(_('Create profile'))
 
 
-class IdentityPickForm(flask_wtf.FlaskForm):
-    identity_uuid = fields.TextField('identity', [])
-
-
-class AvatarForm(flask_wtf.FlaskForm):
-    submit = fields.SubmitField(_('Sign up'), [])
+class ProfilePickForm(flask_wtf.FlaskForm):
+    profile_uuid = fields.TextField('profile', [])
 
 
 class ClaimForm(flask_wtf.FlaskForm):
diff --git a/hiboo/identity/profiles.py b/hiboo/profile/login.py
similarity index 70%
rename from hiboo/identity/profiles.py
rename to hiboo/profile/login.py
index 75ec2f67..b5c79d8c 100644
--- a/hiboo/identity/profiles.py
+++ b/hiboo/profile/login.py
@@ -1,4 +1,4 @@
-from hiboo.identity import blueprint, forms
+from hiboo.profile import blueprint, forms
 from hiboo import models, utils, security
 from hiboo import user as hiboo_user
 from passlib import context, hash
@@ -7,26 +7,24 @@ import flask
 import flask_login
 
 
-@blueprint.route("/profile/pick/<service_uuid>")
+@blueprint.route("/pick/<service_uuid>")
 @security.authentication_required()
-def pick_profile(service_uuid):
+def pick(service_uuid):
     service = models.Service.query.get(service_uuid) or flask.abort(404)
-    service.max_profiles > 0 or flask.abort(404)
-    profiles = models.Identity.filter(service, flask_login.current_user).all()
-    form = forms.IdentityPickForm()
+    profiles = models.Profile.filter(service, flask_login.current_user).all()
+    form = forms.ProfilePickForm()
     return flask.render_template("profile_pick.html", service=service, profiles=profiles, form=form)
 
 
-@blueprint.route("/profile/create_for/<service_uuid>", methods=["GET", "POST"], defaults={"create_for": True}, endpoint="create_profile_for")
-@blueprint.route("/profile/create/<service_uuid>", methods=["GET", "POST"])
+@blueprint.route("/create_for/<service_uuid>", methods=["GET", "POST"], defaults={"create_for": True}, endpoint="create_for")
+@blueprint.route("/create/<service_uuid>", methods=["GET", "POST"])
 @security.authentication_required()
-def create_profile(service_uuid, create_for=False):
+def create(service_uuid, create_for=False):
     service = models.Service.query.get(service_uuid) or flask.abort(404)
-    service.max_profiles > 0 or flask.abort(404)
-    status = models.Identity.ACTIVE
+    status = models.Profile.ACTIVE
     # If the admin passed a user uuid, use that one, otherwise ignore it
     if create_for and flask_login.current_user.is_admin:
-        user = hiboo_user.get_user(intent="identity.create_profile_for", create_for=None)
+        user = hiboo_user.get_user(intent="profile.create_for", create_for=None)
     else:
         user = flask_login.current_user
     # Do not create profile for locked services
@@ -35,7 +33,7 @@ def create_profile(service_uuid, create_for=False):
         return flask.redirect(flask.url_for("account.home"))
     # Other restrictions do not apply to admins or managers
     if not flask_login.current_user.is_admin:
-        profiles = models.Identity.filter(service, user).all()
+        profiles = models.Profile.filter(service, user).all()
         # Do not create profile for locked services
         if service.policy == models.Service.RESERVED:
             flask.flash("You cannot request a profile for this service", "danger")
@@ -50,45 +48,46 @@ def create_profile(service_uuid, create_for=False):
             status = models.Profile.REQUEST
     # Actually display the form
     form = forms.ProfileForm()
+    if service.same_username:
+        form.username.data = user.username
+        form.username.render_kw = {"readonly": True}
     if form.validate_on_submit():
-        existing = models.Identity.query.filter_by(
-            service_uuid=service_uuid, username=form.username.data
-        ).first()
+        username = user.username if service.same_username else form.usenrame.data
+        existing = models.Profile.query.filter_by(service_uuid=service_uuid, username=username).first()
         if existing:
             flask.flash("A profile with that username exists already", "danger")
         else:
-            profile = models.Identity()
-            profile.username = form.username.data
+            profile = models.Profile()
+            profile.username = username
             profile.user = user
             profile.service = service
             profile.comment = form.comment.data
             profile.status = status
             models.db.session.add(profile)
             models.log(models.History.CREATE, profile.username, profile.comment,
-                user=user, service=service, identity=profile)
+                user=user, service=service, profile=profile)
             models.db.session.commit()
             return flask.redirect(utils.url_or_intent("account.home"))
     return flask.render_template("profile_create.html", form=form, service=service,
         user=user, create_for=create_for)
 
 
-@blueprint.route("/profile/claim/<service_uuid>", methods=["GET", "POST"])
+@blueprint.route("/claim/<service_uuid>", methods=["GET", "POST"])
 @security.authentication_required()
-def claim_profile(service_uuid):
+def claim(service_uuid):
     service = models.Service.query.get(service_uuid) or flask.abort(404)
-    service.max_profiles > 0 or flask.abort(404)
     form = forms.ClaimForm()
     if form.validate_on_submit():
-        profile = models.Identity.query.filter_by(
+        profile = models.Profile.query.filter_by(
             service_uuid=service_uuid, username=form.username.data,
-            status = models.Identity.UNCLAIMED
+            status = models.Profile.UNCLAIMED
         ).first()
         check = context.CryptContext([
             scheme for scheme in dir(hash) if not scheme.startswith('__')
         ])
         if profile and check.verify(form.password.data, profile.extra.get("password")):
             profile.user = flask_login.current_user
-            profile.status = models.Identity.ACTIVE
+            profile.status = models.Profile.ACTIVE
             del profile.extra["password"]
             models.db.session.add(profile)
             models.db.session.commit()
@@ -98,10 +97,9 @@ def claim_profile(service_uuid):
     return flask.render_template("profile_claim.html", form=form, service=service)
 
 
-@blueprint.route("/profile/service/<service_uuid>")
+@blueprint.route("/service/<service_uuid>")
 @security.admin_required()
-def list_profiles(service_uuid=None):
+def list(service_uuid=None):
     service = models.Service.query.get(service_uuid) or flask.abort(404)
-    service.max_profiles > 0 or flask.abort(404)
     return flask.render_template("profile_list.html", profiles=service.profiles,
         service=service)
diff --git a/hiboo/identity/templates/profile_claim.html b/hiboo/profile/templates/profile_claim.html
similarity index 100%
rename from hiboo/identity/templates/profile_claim.html
rename to hiboo/profile/templates/profile_claim.html
diff --git a/hiboo/identity/templates/profile_create.html b/hiboo/profile/templates/profile_create.html
similarity index 100%
rename from hiboo/identity/templates/profile_create.html
rename to hiboo/profile/templates/profile_create.html
diff --git a/hiboo/identity/templates/profile_list.html b/hiboo/profile/templates/profile_list.html
similarity index 83%
rename from hiboo/identity/templates/profile_list.html
rename to hiboo/profile/templates/profile_list.html
index 762ef2df..111757bb 100644
--- a/hiboo/identity/templates/profile_list.html
+++ b/hiboo/profile/templates/profile_list.html
@@ -12,7 +12,7 @@
           <tr>
             <th>{% trans %}Service{% endtrans %}</th>
             <th>{% trans %}User{% endtrans %}</th>
-            <th>{% trans %}Identity{% endtrans %}</th>
+            <th>{% trans %}Profile username{% endtrans %}</th>
             <th>{% trans %}Created on{% endtrans %}</th>
           </tr>
           {% for profile in profiles %}
@@ -32,6 +32,6 @@
 
 {% block actions %}
 {% if service %}
-<a href="{{ url_for(".create_profile_for", service_uuid=service.uuid) }}" class="btn btn-success">{% trans %}Create a profile{% endtrans %}</a>
+<a href="{{ url_for(".create_for", service_uuid=service.uuid) }}" class="btn btn-success">{% trans %}Create a profile{% endtrans %}</a>
 {% endif %}
 {% endblock %}
diff --git a/hiboo/identity/templates/profile_pick.html b/hiboo/profile/templates/profile_pick.html
similarity index 90%
rename from hiboo/identity/templates/profile_pick.html
rename to hiboo/profile/templates/profile_pick.html
index 41f19fe5..57ed0005 100644
--- a/hiboo/identity/templates/profile_pick.html
+++ b/hiboo/profile/templates/profile_pick.html
@@ -15,7 +15,7 @@
         {% if profile.status == "active" %}
         <form method="POST" action="{{ utils.url_or_intent("account.home") }}" class="form">
             {{ form.hidden_tag() }}
-            <input type="hidden" name="identity_uuid" value="{{ profile.uuid }}">
+            <input type="hidden" name="profile_uuid" value="{{ profile.uuid }}">
             <input type="submit" value="Sign in" style="opacity: 0.8" class="btn btn-lg btn-flat bg-gray text-black pull-right">
         </form>
         {% elif profile.status == "blocked" %}
@@ -62,12 +62,12 @@
 
 {% block actions %}
 {% if service.policy not in ("locked") %}
-<a href="{{ utils.url_for(".claim_profile", intent=True) }}" class="btn btn-primary">Claim profile</a>
+<a href="{{ utils.url_for(".claim", intent=True) }}" class="btn btn-primary">Claim profile</a>
 {% endif %}
 {% if service.policy in ("open", "burst") and profiles.__len__() < service.max_profiles %}
-<a href="{{ utils.url_for(".create_profile", intent=True) }}" class="btn btn-success">Create profile</a>
+<a href="{{ utils.url_for(".create", intent=True) }}" class="btn btn-success">Create profile</a>
 {% elif service.policy in ("managed", "burst") %}
-<a href="{{ utils.url_for(".create_profile", intent=True) }}" class="btn btn-warning">Request profile</a>
+<a href="{{ utils.url_for(".create", intent=True) }}" class="btn btn-warning">Request profile</a>
 {% else %}
 <a href="#" class="btn btn-success" disabled>Create profile</a>
 {% endif %}
diff --git a/hiboo/service/forms.py b/hiboo/service/forms.py
index bdf19bcf..cd7fafce 100644
--- a/hiboo/service/forms.py
+++ b/hiboo/service/forms.py
@@ -15,5 +15,6 @@ class ServiceForm(flask_wtf.FlaskForm):
     policy = fields.SelectField(_('Profile policy'),
         choices=list(models.Service.POLICIES.items()))
     max_profiles = fields.IntegerField(_('Maximum profile count'),
-        [validators.NumberRange(0, 1000)])
+        [validators.NumberRange(1, 1000)])
+    same_username = fields.BooleanField(_('Disable per-profile username'))
     submit = fields.SubmitField(_('Submit'))
diff --git a/hiboo/service/templates/service_details.html b/hiboo/service/templates/service_details.html
index e690ad06..af2bcfd7 100644
--- a/hiboo/service/templates/service_details.html
+++ b/hiboo/service/templates/service_details.html
@@ -28,7 +28,7 @@
 {% endblock %}
 
 {% block actions %}
-<a href="{{ url_for("identity.list_profiles", service_uuid=service.uuid) }}" class="btn btn-primary">{% trans %}View profiles{% endtrans %}</a>
+<a href="{{ url_for("profile.list", service_uuid=service.uuid) }}" class="btn btn-primary">{% trans %}View profiles{% endtrans %}</a>
 <a href="{{ url_for(".create") }}" class="btn btn-primary">{% trans %}Edit this service{% endtrans %}</a>
 <a href="{{ url_for(".delete", service_uuid=service.uuid) }}" class="btn btn-danger">{% trans %}Delete this service{% endtrans %}</a>
 {% endblock %}
diff --git a/hiboo/sso/oidc.py b/hiboo/sso/oidc.py
index 4531dfab..2a832638 100644
--- a/hiboo/sso/oidc.py
+++ b/hiboo/sso/oidc.py
@@ -5,7 +5,7 @@ from authlib.oauth2.rfc6749 import grants as grants_oauth2
 from authlib.oidc import core as oidc
 
 from hiboo.sso import forms, blueprint
-from hiboo import models, utils, identity
+from hiboo import models, utils, profile
 
 import flask
 
@@ -108,7 +108,7 @@ class AuthorizationCodeGrant(grants_oauth2.AuthorizationCodeGrant):
         models.db.session.commit()
 
     def authenticate_user(self, authorization_code):
-        profile = models.Identity.query.get(authorization_code.user_id)
+        profile = models.Profile.query.get(authorization_code.user_id)
         return profile
 
 
@@ -155,10 +155,10 @@ def oidc_authorize(service_uuid):
     # Get the profile from user input (implies redirects)
     service = models.Service.query.get(service_uuid) or flask.abort(404)
     service.protocol == "oidc" or flask.abort(404)
-    profile = identity.get_identity(service, intent=True) or flask.abort(403)
+    picked = profile.get_profile(service, intent=True) or flask.abort(403)
     # Generate and return the response
     client = Client(service)
-    return client.authorization.create_authorization_response(grant_user=profile)
+    return client.authorization.create_authorization_response(grant_user=picked)
 
 
 @blueprint.route("/token/<service_uuid>", methods=["POST"])
diff --git a/hiboo/sso/saml.py b/hiboo/sso/saml.py
index c62b6cb6..56800701 100644
--- a/hiboo/sso/saml.py
+++ b/hiboo/sso/saml.py
@@ -6,7 +6,7 @@ from saml2 import sigver
 sigver.security_context = security_context
 
 from hiboo.sso import blueprint, forms
-from hiboo import models, utils, identity, security
+from hiboo import models, utils, profile, security
 from saml2 import server, saml, config, mdstore, assertion
 from cryptography import x509
 from cryptography.hazmat import primitives, backends
@@ -159,7 +159,7 @@ def saml_redirect(service_uuid):
     # Get the profile from user input (implies redirects)
     service = models.Service.query.get(service_uuid) or flask.abort(404)
     service.protocol == "saml" or flask.abort(404)
-    profile = identity.get_identity(service, intent=True) or flask.abort(403)
+    picked = profile.get_profile(service, intent=True) or flask.abort(403)
     # Parse the authentication request and check the ACS
     idp = server.Server(config=(MetaData.get_config(service)))
     xml = flask.request.args["SAMLRequest"]
@@ -169,13 +169,13 @@ def saml_redirect(service_uuid):
     # Provide a SAML response
     response = idp.create_authn_response(
         identity={
-            'uid': profile.username,
-            'email': profile.email
+            'uid': picked.username,
+            'email': picked.email
         },
         in_response_to=request.message.id,
         destination=service.config["acs"],
         sp_entity_id=service.config["entityid"],
-        userid=profile.username,
+        userid=picked.username,
         authn={'class_ref': saml2.saml.AUTHN_PASSWORD},
         sign_assertion=True
     )
-- 
GitLab