Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • acides/hiboo
  • frju365/hiboo
  • pascoual/hiboo
  • thedarky/hiboo
  • jeremy/hiboo
  • cyrinux/hiboo
  • a.f/hiboo
  • mickge/hiboo
  • llaq/hiboo
  • vaguelysalaried/hiboo
  • felinn-glotte/hiboo
  • AntoninDelFabbro/hiboo
  • docemmetbrown/hiboo
13 results
Show changes
Showing
with 380 additions and 244 deletions
{% extends "base.html" %}
{% block title %}{% trans %}My account{% endtrans %}{% endblock %}
{% block subtitle %}{% trans %}my profiles{% endtrans %}{% endblock %}
{% block title %}
{% trans %}My account{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}my profiles{% endtrans %}
{% endblock %}
{% block content %}
<div class="row">
{% for service, profile in profiles %}
{% set service = profile.service %}
{% set status = profile.STATUSES[profile.status] %}
<div class="col-md-4 col-s-6 col-xs-12">
<div class="box box-widget widget-user-2">
<div class="widget-user-header bg-{{ macros.colors[loop.index0 % 7] }}">
<span class="btn btn-lg btn-flat bg-gray }} text-black pull-right">{{ status[1] | capitalize }}</span>
<h3 class="widget-header-username">{{ service.name }} / {{ profile.username }}</h3>
<h5 class="widget-header-desc">{{ profile.comment or "No profile description" }}&nbsp;</h5>
<div class="row g-3">
{% for service, profile in profiles %}
{% set service = profile.service %}
{% set status = profile.STATUSES[profile.status] %}
<div class="col-xs col-md-6 col-lg-4">
<div class="card">
<div class="card-header text-white"
style="background-color: var(--bs-{{ macros.colors[loop.index0 % 7] }})">
<h3>{{ service.name }} / {{ profile.name }}</h3>
{% if profile.comment %}
<h5>{{ profile.comment }}</h5>
{% else %}
<h5>{% trans %}No profile description{% endtrans %}</h5>
{% endif %}
</div>
<div class="card-body">
<div class="py-2 px-3">
{{ macros.profile_status(profile) }}
</div>
<div class="list-group list-group-flush">
{% if not service.single_profile %}
{% if service.policy in ("open", "burst") and profile.__class__.filter(service, current_user).count() < service.max_profiles %}
<a class="list-group-item list-group-item-action"
href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}">
{{ render_icon("plus-circle-fill") }}
{% trans %}Create another profile{% endtrans %}</a>
{% elif service.policy in ("managed", "burst") %}
<a class="list-group-item list-group-item-action"
href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}">
{{ render_icon("question-circle-fill") }}
{% trans %}Request another profile{% endtrans %}</a>
{% endif %}
{% if service.policy not in ("locked",) %}
<a class="list-group-item list-group-item-action"
href="{{ utils.url_for("profile.claim", service_uuid=service.uuid) }}">
{{ render_icon("hand-index-fill") }}
{% trans %}Claim another profile{% endtrans %}</a>
{% endif %}
{% endif %}
<a class="list-group-item list-group-item-action"
href="{{ utils.url_for("profile.edit", profile_uuid=profile.uuid) }}">
{{ render_icon("pen-fill") }}
{% trans %}Edit this profile{% endtrans %}</a>
{% if profile.transition_step %}
{% set transition = profile.TRANSITIONS[profile.transition] %}
{% set color, status = profile.STATUSES[transition.to] %}
{% set when = transition.delta(profile) %}
<a class="list-group-item list-group-item-action" href="#">
{{ render_icon("clock") }}
{% trans status, when %}Profile will be {{ status }} in {{ when }}{% endtrans %}</a>
{% endif %}
{% for action in profile.actions %}
<a class="list-group-item list-group-item-action"
href="{{ action.url(profile=profile) }}">
{{ render_icon(action.icon) }}
{{ action.description | capitalize }}</a>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="box-footer no-padding">
<ul class="nav nav-stacked">
{% if not service.single_profile %}
{% if service.policy in ("open", "burst") and profile.__class__.filter(service, current_user).count() < service.max_profiles %}
<li>
<a href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}">
<i class="fa fa-user-plus"></i>&nbsp;
{% trans %}Create another profile{% endtrans %}
</a>
</li>
{% elif service.policy in ("managed", "burst") %}
<li><a href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}"><i class="fa fa-user-plus"></i>&nbsp;&nbsp;{% trans %}Request another profile{% endtrans %}</a></li>
{% endif %}
{% if service.policy not in ("locked",) %}
<li><a href="{{ utils.url_for("profile.claim", service_uuid=service.uuid) }}"><i class="fa fa-user-plus"></i>&nbsp;&nbsp;{% trans %}Claim another profile{% endtrans %}</a></li>
{% endif %}
{% endif %}
{% if profile.transition %}
{% set transition = profile.TRANSITIONS[profile.transition] %}
{% set color, status = profile.STATUSES[transition.to] %}
{% set when = transition.delta(profile) %}
<li>
<a href="#">
<i class="fa fa-clock-o"></i>&nbsp;
{% trans status, when %}Profile will be {{ status }} in {{ when }}{% endtrans %}
</a>
</li>
{% endif %}
{% for action in common.get_actions(profile) %}
<li><a href="{{ action.url(profile=profile) }}"><i class="fa fa-trash"></i>&nbsp;&nbsp;{{ action.description | capitalize }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}
\ No newline at end of file
{% endblock %}
{% extends "form.html" %}
{% block title %}{% trans %}Reset your password{% endtrans %}{% endblock %}
{% extends "base.html" %}
{% block title %}{% trans %}Sign in{% endtrans %}{% endblock %}
{% block subtitle %}{% trans %}to access your account{% endtrans %}{% endblock %}
{% block content %}
{{ macros.form(form) }}
{% endblock %}
{% block actions %}
<a href="{{ utils.url_for(".signup") }}" class="btn btn-success">{% trans %}Sign up{% endtrans %}</a>
{% endblock %}
{% extends "base.html" %}
{% block title %}
{% trans %}Sign in{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}to access your account{% endtrans %}
{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col-lg-6">
{{ render_form(form, form_type='horizontal', horizontal_columns=('lg', 3, 9) ) }}
</div>
</div>
{% if config['OPEN_SIGNUP'] %}
{% set signup_url = url_for("account.signup") %}
<br><p class="text-secondary">{% trans %}If you don't already have an account, you can <a href="{{ signup_url }}">sign up</a> now!{% endtrans %}</p>
{% endif %}
{% endblock %}
{% extends "form.html" %}
{% block title %}
{% trans %}Time-based one-time password (TOTP) verify{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}to access your account{% endtrans %}
{% endblock %}
{% block actions %}
<a href="{{ utils.url_for(".signup") }}" class="btn btn-success">
{% trans %}Sign up{% endtrans %}</a>
{% endblock %}
{% extends "form.html" %}
{% block title %}{% trans %}Sign up{% endtrans %}{% endblock %}
{% block subtitle %}{% trans %}for a new account{% endtrans %}{% endblock %}
{% block content %}
{{ macros.form(form) }}
{% block title %}
{% trans %}Sign up{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}for a new account{% endtrans %}
{% endblock %}
......@@ -12,11 +12,15 @@ class Action:
action = None
def __init__(self, label=None, description=None, action=None, *args, **kwargs):
def __init__(self, label=None, label_alt=None, description=None, icon=None, action=None, *args, **kwargs):
if label is not None:
self.label = label
if label_alt is not None:
self.label_alt = label_alt
if description is not None:
self.description = description
if icon is not None:
self.icon = icon
if action is not None:
self.action = action
......@@ -75,11 +79,12 @@ class CancelTransition(Action):
"""
action = "profile.cancel_transition"
label = _("cancel")
description = _("cancel ongoing profile actions")
label = (_("cancel"))
description = (_("cancel ongoing profile actions"))
icon="power-off"
def url(self, profile):
return super(CancelTransition, self).url(profile_uuid=profile.uuid)
def authorized(self, profile):
return profile.transition_step
\ No newline at end of file
return profile.transition_step
from hiboo import sso, models, format
from wtforms import validators, fields, widgets
from flask_babel import lazy_gettext as _
from flask_bootstrap import SwitchField
import flask
import flask_wtf
......@@ -18,16 +19,12 @@ class BaseForm(flask_wtf.FlaskForm):
[validators.NumberRange(1, 1000)])
profile_format = fields.SelectField(_('Profile username format'),
choices=(
[("", _("Default ({})".format(
format.ProfileFormat.registry[None].message))
)] +
[(name, format.message.capitalize())
for name, format in format.ProfileFormat.registry.items()
if name is not None
for name, format in format.NameFormat.registry.items()
]
)
)
single_profile = fields.BooleanField(_('Enable single-profile behavior (no custom username, no additional profile)'))
single_profile = SwitchField(_('Enable single-profile behavior (no custom username, no additional profile)'))
submit = fields.SubmitField(_('Submit'))
......
......@@ -8,7 +8,7 @@ class GitlabApplication(base.OIDCApplication):
""" Gitlab is a source code and project management plaform, largely based on Git
"""
name = _("Gitlab")
name = (_("Gitlab"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("Gitlab URL"), [validators.URL(require_tld=False)])
......@@ -30,7 +30,7 @@ class GrafanaApplication(base.OIDCApplication):
""" Grafana is an infrastructure dashboard application
"""
name = _("Grafana")
name = (_("Grafana"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("Grafana URL"), [validators.URL(require_tld=False)])
......
......@@ -5,7 +5,7 @@ from flask_babel import lazy_gettext as _
import flask
import flask_wtf
import axon.cli
import requests
@register("mastodon")
......@@ -13,7 +13,7 @@ class MastodonApplication(base.SAMLApplication):
""" Mastodon social network is a popular micro-blogging platform compliant with ActivityPub
"""
name = _("Mastodon")
name = (_("Mastodon"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("Mastodon URL"), [validators.URL(require_tld=False)])
......@@ -33,7 +33,7 @@ class SynapseApplication(base.SAMLApplication):
""" Synapse is the flagship implmentation of Matrix, a federated, end-to-end-encrypted messaging protocol
"""
name = _("Synapse")
name = (_("Synapse"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("Synapse homeserver URL"), [validators.URL(require_tld=False)])
......@@ -48,31 +48,35 @@ class SynapseApplication(base.SAMLApplication):
"sign_mode": "response"
}
def get_axon(self, service):
""" Return an axon instance for the homeserver
def api(self, service, method, path, params={}, data={}):
""" Call the synapse admin API, returns the parsed JSON object
"""
return axon.api.Client(dict(
url=service.config.get("application_uri"),
token=service.config.get("admin_token")
))
@base.action("Search rooms")
def search_rooms(self, service):
""" Search by keyword among server rooms, including non-published
url = service.config.get("application_uri") + "/_synapse/admin/" + path
headers = {"Authorization": "Bearer " + service.config.get("admin_token")}
return getattr(requests, method)(url, headers=headers, params=params, json=data).json()
@base.action("List rooms")
def list_rooms(self, service):
""" List or search by keyword among server rooms, including non-published
rooms
"""
class SearchForm(flask_wtf.FlaskForm):
keyword = fields.StringField(_("Search keyword"))
submit = fields.SubmitField(_("Search"))
mxid = flask.request.values.get("mxid")
form = SearchForm()
if form.validate_on_submit():
client = self.get_axon(service)
results = client.list_rooms(False, search_term=form.keyword.data)
if mxid:
rooms = [
self.api(service, "get", "v1/rooms/{}".format(roomid))
for roomid in
self.api(service, "get", "v1/users/{}/joined_rooms".format(mxid)).get("joined_rooms")
]
elif form.validate_on_submit():
rooms = self.api(service, "get", "v1/rooms", {"search_term": form.keyword.data}).get("rooms")
else:
results = []
rooms = []
return flask.render_template(
"application_synapse/rooms.html", form=form, results=results,
service=service
"application_synapse/rooms.html", form=form, rooms=rooms, service=service
)
@base.action("Get room details")
......@@ -85,17 +89,8 @@ class SynapseApplication(base.SAMLApplication):
roomid = flask.request.values.get("roomid")
form = RoomForm(data=dict(roomid=roomid))
if roomid:
client = self.get_axon(service)
room = client.get_room(roomid)
members = {}
suffix = ":" + service.config.get("domain")
for member in client.get_room_members(roomid).get("members", []):
if member.endswith(suffix):
members[member] = service.profiles.filter_by(
username=member[1:-len(suffix)]
).first()
else:
members[member] = None
room = self.api(service, "get", "v1/rooms/{}".format(roomid))
members = self.api(service, "get", "v1/rooms/{}/members".format(roomid))
else:
room = members = None
return flask.render_template(
......@@ -103,33 +98,61 @@ class SynapseApplication(base.SAMLApplication):
members=members, service=service
)
@base.action("Get user details", profile=True)
def get_user_details(self, profile):
@base.action("Get user details")
def get_user(self, service):
""" Display some user details
"""
client = self.get_axon(profile.service)
mxid = "@" + profile.username + ":" + profile.service.config.get("domain")
user = client.get_user(mxid)
user.update(client.get_user_rooms(mxid))
class UserForm(flask_wtf.FlaskForm):
mxid = fields.StringField(_("MXID"))
submit = fields.SubmitField(_("Display"))
mxid = flask.request.values.get("mxid")
form = UserForm(data=dict(mxid=mxid))
if mxid:
user = self.api(service, "get", "v2/users/{}".format(mxid))
devices = self.api(service, "get", "v2/users/{}/devices".format(mxid))
else:
user = devices = None
return flask.render_template(
"application_synapse/user.html", user=user, profile=profile
"application_synapse/user.html", service=service, form=form, user=user, devices=devices
)
@base.action("List medias")
def list_media(self, service):
""" List media for user or room
"""
roomid = flask.request.values.get("roomid")
mxid = flask.request.values.get("mxid")
uri = service.config.get("application_uri") + "/_matrix/media/r0/{}/{}"
hs = service.config.get("domain")
paths = []
if roomid:
raw = self.api(service, "get", "v1/room/{}/media".format(roomid))
paths = [hs + "/" + local.rsplit("/", 1)[-1] for local in raw["local"]] + \
[remote.split("//")[1] for remote in raw["remote"]]
elif mxid:
raw = self.api(service, "get", "v1/users/{}/media".format(mxid))
paths = [hs + "/" + media["media_id"] for media in raw["media"]]
media = {
uri.format("download", path): uri.format("thumbnail", path) + "?width=50&height=50&method=crop"
for path in reversed(paths[-100:])
}
return flask.render_template(
"application_synapse/media.html", service=service, media=media
)
@base.hook(models.Profile.START, "delete", "delete-blocked")
def delete_without_purging(self, profile):
""" Delete a user account from Matrix
"""
client = self.get_axon(profile.service)
mxid = "@" + profile.username + ":" + profile.service.config.get("domain")
client.delete_user(mxid, erase=False)
mxid = "@" + profile.name + ":" + profile.service.config.get("domain")
self.api(profile.service, "post", "v1/deactivate/" + mxid, data=dict(erase=False))
@base.hook(models.Profile.START, "purge", "purge-blocked", "purge-deleted")
def delete_with_purging(self, profile):
""" Purge a user account from Matrix
"""
client = self.get_axon(profile.service)
mxid = "@" + profile.username + ":" + profile.service.config.get("domain")
client.delete_user(mxid, erase=True)
mxid = "@" + profile.name + ":" + profile.service.config.get("domain")
self.api(profile.service, "post", "v1/deactivate/" + mxid, data=dict(erase=True))
@register("writefreely")
......@@ -137,7 +160,7 @@ class WriteFreelyApplication(base.OIDCApplication):
""" WriteFreely is free, open source, ActivityPub compliant blog software.
"""
name = _("WriteFreely")
name = (_("WriteFreely"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("WriteFreely URL"), [validators.URL(require_tld=False)])
......@@ -159,7 +182,7 @@ class PeertubeApplication(base.OIDCApplication):
""" PeerTube is free, open source, video publishing software
"""
name = _("PeerTube")
name = (_("PeerTube"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("PeerTube URL"), [validators.URL(require_tld=False)])
......@@ -173,3 +196,22 @@ class PeertubeApplication(base.OIDCApplication):
"grant_types": ["authorization_code"],
"response_types": ["code"],
}
@register("flarum")
class FlarumApplication(base.SAMLApplication):
""" Flarum is a simple discussion platform, fast and easy to use, with all the features you need to run a successful community.
"""
name = (_("Flarum"))
class Form(base.BaseForm):
application_uri = fields.StringField(_("Flarum URL"), [validators.URL(require_tld=False)])
submit = fields.SubmitField(_('Submit'))
def configure(self, form):
return {
"acs": form.application_uri.data + "/auth/saml/acs",
"entityid": form.application_uri.data + "/auth/saml/metadata",
"sign_mode": "response"
}
......@@ -8,7 +8,7 @@ class GenericOIDCApplication(base.OIDCApplication):
""" OpenID Connect (OIDC) is JWT based authentication and authorization protocol
"""
name = _("Generic OIDC")
name = (_("Generic OIDC"))
class Form(base.BaseForm):
redirect_uri = fields.StringField(_("Redirect URI"), [validators.URL(require_tld=False)])
......@@ -22,7 +22,9 @@ class GenericOIDCApplication(base.OIDCApplication):
grant_types = fields.SelectMultipleField(
_('OpenID Connect grant type'), choices=[
("authorization_code", _("Authorization Code")),
# Translators: this qualifier refers to an OIDC grant type.
("implicit", _("Implicit")),
# Translators: this qualifier refers to an OIDC grant type.
("hybrid", _("Hybrid"))
]
)
......@@ -53,7 +55,7 @@ class GenericSAMLApplication(base.SAMLApplication):
""" SAML2 is a legacy protocol based on XML security. Only redirect/post binding is supported
"""
name = _("Generic SAML2")
name = (_("Generic SAML2"))
class Form(base.BaseForm):
entityid = fields.StringField(_('SP entity id'), [validators.URL(require_tld=False)])
......
......@@ -3,35 +3,63 @@ from wtforms import validators, fields
from flask_babel import lazy_gettext as _
@register("nextcloud")
@register("nextcloud_SAML")
class NextcloudApplication(base.SAMLApplication):
""" NextCloud is a free alternative to many cloud vendors (storage, contacts, meetings, etc.)
"""
NextCloud (SAML) template is deprecated. OIDC authentication used in
NextCloud template is prefered.
"""
name = _("NextCloud")
name = _("NextCloud (SAML)")
class Form(base.BaseForm):
application_uri = fields.StringField(_("NextCloud URL"), [validators.URL(require_tld=False)])
submit = fields.SubmitField(_('Submit'))
application_uri = fields.StringField(
_("NextCloud URL"), [validators.URL(require_tld=False)]
)
submit = fields.SubmitField(_("Submit"))
def configure(self, form):
return {
"acs": form.application_uri.data + "/apps/user_saml/saml/acs",
"entityid": form.application_uri.data + "/apps/user_saml/saml/metadata",
"sign_mode": "response"
"sign_mode": "response",
}
@register("nextcloud")
class NextcloudApplication(base.OIDCApplication):
"""NextCloud is a free alternative to many cloud vendors (storage, contacts, meetings, etc.)"""
name = _("NextCloud")
class Form(base.BaseForm):
application_uri = fields.StringField(
_("NextCloud URL"), [validators.URL(require_tld=False)]
)
submit = fields.SubmitField(_("Submit"))
def configure(self, form):
callback_uri = form.application_uri.data + "/apps/user_oidc/code"
return {
"token_endpoint_auth_method": "client_secret_post",
"redirect_uris": [callback_uri],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"special_mappings": ["mask_sub_uuid"],
}
@register("seafile")
class SeafileApplication(base.OIDCApplication):
""" Seafile is a file sharing and synchronization application with an embedded Web viewer
"""
"""Seafile is a file sharing and synchronization application with an embedded Web viewer"""
name = _("Seafile")
class Form(base.BaseForm):
application_uri = fields.StringField(_("Seafile URL"), [validators.URL(require_tld=False)])
submit = fields.SubmitField(_('Submit'))
application_uri = fields.StringField(
_("Seafile URL"), [validators.URL(require_tld=False)]
)
submit = fields.SubmitField(_("Submit"))
def configure(self, form):
callback_uri = form.application_uri.data + "/oauth/callback"
......@@ -40,5 +68,5 @@ class SeafileApplication(base.OIDCApplication):
"redirect_uris": [callback_uri],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"special_mappings": []
"special_mappings": [],
}
<p>Flarum uses the <a href="https://github.com/askvortsov1/flarum-saml">flarum_saml</a>
extension in order to handle SAML2 authentication.</p>
<p>You must first install this extension on your instance, then enable the <i>SAML2 SSO</i>
extension in the <i>Administration</i> panel and fill in the following parameters.</p>
<dl>
<dt>Identity Provider Metadata URL (Alternative to XML)</dt>
<dd><code>{{ url_for("sso.saml_metadata", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt>NameID format</dt>
<dd><code>Persistent</code></dd>
<dt>Attribute to map the username to</dt>
<dd><code>urn:oid:0.9.2342.19200300.100.1.1</code></dd>
<dt>Sign authn requests?</dt>
<dd><code>Yes</code></dd>
<dt>Sign logout requests?</dt>
<dd><code>Yes</code></dd>
<dt>Sign logout responses?</dt>
<dd><code>Yes</code></dd>
<dt>Sign metadata?</dt>
<dd><code>Yes</code></dd>
<dt>Want Assertions Encrypted?</dt>
<dd><code>Not supported</code></dd>
<dt>Want Assertions Signed?</dt>
<dd><code>Not supported</code></dd>
<dt>Want Messages Signed?</dt>
<dd><code>Yes</code></dd>
<dt>Enable SLO? If your IDP does not support this, this will do nothing.</dt>
<dd><code>Yes</code></dd>
<dt>Use SAML2 SSO as only login option?</dt>
<dd><code>Remember to have an SAML user as Flarum admin if enabled</code></dd>
<dt>x509 Private Key (needed for signing, if enabled)</dt>
<dd><pre><code>{{ "".join(service.config["sp_key"].strip().split("\n")[1:-1]) }}</code></pre></dd>
<dt>x509 Certificate (needed for signing, if enabled)</dt>
<dd><pre><code>{{ "".join(service.config["sp_cert"].strip().split("\n")[1:-1]) }}</code></pre></dd>
</dl>
{% include "application_saml.html" %}
<h3>Setting up Gitlab</h3>
<p>Gitlab supports OIDC authentication using the Omniauth::oidc module.</p>
<p>If you are using Omnibus, you may paste the following config directly in your `gitlab.rb`.</p>
<pre>
# Authentication
<p>If you are using Omnibus, you may paste the following config directly in your <code>gitlab.rb</code>:</p>
<pre class="bg-dark-subtle rounded p-2"><code># Authentication
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect']
gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'openid_connect'
......@@ -26,13 +24,7 @@ gitlab_rails['omniauth_providers'] = [
}
}
}
]
</pre>
<p></p>You will also need to provision your users Omniauth bindings, by running the following SQL query against your Gitlab database.</p>
<pre>
insert into identities (extern_uid,provider,user_id,created_at,updated_at) (select users.username as extern_uid, 'openid_connect' as provider, users.id as user_id, now() created_at, now() updated_at from users);
</pre>
{% include "application_oidc.html" %}
\ No newline at end of file
]</code></pre>
<p>You will also need to provision your users Omniauth bindings, by running the following SQL query against your Gitlab database:</p>
<p><code>insert into identities (extern_uid,provider,user_id,created_at,updated_at) (select users.username as extern_uid, 'openid_connect' as provider, users.id as user_id, now() created_at, now() updated_at from users);</code></p>
{% include "application_oidc.html" %}
<h3>Setting up Grafana</h3>
<p>Grafna supports Oauth2 authentication through Omniauth, which is compatible with OIDC.</p>
<p>If you are running Grafana directly, you may add the following lines to your configuration.</p>
<pre>
[server]
<pre class="bg-dark-subtle rounded p-2"><code>[server]
root_url = {{ service.config["application_uri"] }}
[auth.generic_oauth]
......@@ -12,19 +10,14 @@ client_id = {{ service.config["client_id"] }}
client_secret = {{ service.config["client_secret"] }}
scopes = openid profile email
auth_url = {{ url_for("sso.oidc_authorize", service_uuid=service.uuid, _external=True) }}
token_url = {{ url_for("sso.oidc_token", service_uuid=service.uuid, _external=True) }}
</pre>
token_url = {{ url_for("sso.oidc_token", service_uuid=service.uuid, _external=True) }}</code></pre>
<p>If you are running the Grafana Docker image, please set the following environment variables.</p>
<pre>
GF_SERVER_ROOT_URL={{ service.config["application_uri"] }}
<pre class="bg-dark-subtle rounded p-2"><code>GF_SERVER_ROOT_URL={{ service.config["application_uri"] }}
GF_AUTH_GENERIC_OAUTH_ENABLED=True
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=True
GF_AUTH_GENERIC_OAUTH_CLIENT_ID={{ service.config["client_id"] }}
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET={{ service.config["client_secret"] }}
GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email
GF_AUTH_GENERIC_OAUTH_AUTH_URL={{ url_for("sso.oidc_authorize", service_uuid=service.uuid, _external=True) }}
GF_AUTH_GENERIC_OAUTH_TOKEN_URL={{ url_for("sso.oidc_token", service_uuid=service.uuid, _external=True) }}
</pre>
{% include "application_oidc.html" %}
\ No newline at end of file
GF_AUTH_GENERIC_OAUTH_TOKEN_URL={{ url_for("sso.oidc_token", service_uuid=service.uuid, _external=True) }}</code></pre>
{% include "application_oidc.html" %}
<h3>Setting up Mastodon</h3>
<p>Mastodon uses SAML for SSO authentication. The example configuration is available at the following URL : <a href="https://github.com/tootsuite/mastodon/blob/master/.env.production.sample">https://github.com/tootsuite/mastodon/blob/master/.env.production.sample</a>.</p>
<p>Mastodon uses SAML for SSO authentication. The example configuration is available at the following URL :<br>
<a href="https://github.com/tootsuite/mastodon/blob/master/.env.production.sample">https://github.com/tootsuite/mastodon/blob/master/.env.production.sample</a>.</p>
<p>In order to configure SAML for Mastodon, you may copy then paste the following lines directly into your Mastodon environment.</p>
<pre>
# Authentication
<pre class="bg-dark-subtle rounded p-2"><code># Authentication
OAUTH_REDIRECT_AT_SIGN_IN=true
SAML_ENABLED=true
SAML_ISSUER={{ service.config["sp_entityid"] }}
......@@ -16,7 +15,5 @@ SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
SAML_IDP_SSO_TARGET_URL={{ url_for("sso.saml_redirect", service_uuid=service.uuid, _external=True) }}
SAML_IDP_CERT={{ "".join(service.config["idp_cert"].strip().split("\n")[1:-1]) }}
SAML_CERT={{ "".join(service.config["sp_cert"].strip().split("\n")[1:-1]) }}
SAML_PRIVATE_KEY={{ "".join(service.config["sp_key"].strip().split("\n")[1:-1]) }}
</pre>
{% include "application_saml.html" %}
\ No newline at end of file
SAML_PRIVATE_KEY={{ "".join(service.config["sp_key"].strip().split("\n")[1:-1]) }}</code></pre>
{% include "application_saml.html" %}
<h3>Setting up NextCloud</h3>
<p>NextCloud uses the <a href="https://apps.nextcloud.com/apps/user_saml">user_saml</a> extension in order to handle SAML2 authentication.</p>
<p>You must first install this extension on your instance, then go to your <i>Settings</i> menu and fill in the following parameters.</p>
<dt>Attribute to map the uid to</dt>
<dd><pre>urn:oid:0.9.2342.19200300.100.1.1</pre></dd>
<dt>Name id format</dt>
<dd><pre>Persistent</pre></dd>
<dt>X.509 certificat of the Service Provider</dt>
<dd><pre>{{ "".join(service.config["sp_cert"].strip().split("\n")[1:-1]) }}</pre></dd>
<dt>Private Key of the Service Provider</dt>
<dd><pre>{{ "".join(service.config["sp_key"].strip().split("\n")[1:-1]) }}</pre></dd>
<dt>Identifier of the IDP entity</dt>
<dd><pre>{{ url_for("sso.saml_metadata", service_uuid=service.uuid, _external=True) }}</pre></dd>
<dt>URL target of the IDP where the SP will send the Authentication Request Message</dt>
<dd><pre>{{ url_for("sso.saml_redirect", service_uuid=service.uuid, _external=True) }}</pre></dd>
<dt>URL location of the IDP where the SP will send the SLO request</dt>
<dd><pre>{{ service.config["application_uri"] }}</pre></dd>
<dt>Public X.509 certificat of the IDP</dt>
<dd><pre>{{ "".join(service.config["idp_cert"].strip().split("\n")[1:-1]) }}</pre></dd>
{% include "application_saml.html" %}
\ No newline at end of file
<p>Nextcloud uses the <a href="https://apps.nextcloud.com/apps/user_oidc">user_oidc</a>
extension in order to handle OIDC authentication.</p>
<p>You must first install this extension on your instance, then go to your
<code>Settings > OpenID Connect</code> menu and fill in the following parameters.</p>
<dl>
<dt>Client ID</dt>
<dd><code>{{ service.config["client_id"] }}</code></dd>
<dt>Client secret</dt>
<dd><code>{{ service.config["client_secret"] }}</code></dd>
<dt>Discovery endpoint</dt>
<dd><code>{{ url_for("sso.oidc_discovery", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt>Scope</dt>
<dd><code>openid email profile</code></dd>
</dl>
<div class="alert alert-info">
If you plan to migrate from user_saml and/or native backend, uncheck <code>Use unique user id</code>.
</div>
{% include "application_oidc.html" %}
<div class="alert alert-warning">
NextCloud SAML authentication is deprecated. OIDC authentication used in Nextcloud template is prefered
</div>
<p>NextCloud uses the <a href="https://apps.nextcloud.com/apps/user_saml">user_saml</a>
extension in order to handle SAML2 authentication.</p>
<p>You must first install this extension on your instance, then go to your <i>Settings</i>
menu and fill in the following parameters.</p>
<dl>
<dt>Attribute to map the uid to</dt>
<dd><code>urn:oid:0.9.2342.19200300.100.1.1</code></dd>
<dt>Name id format</dt>
<dd><code>Persistent</code></dd>
<dt>X.509 certificat of the Service Provider</dt>
<dd><pre><code>{{ "".join(service.config["sp_cert"].strip().split("\n")[1:-1]) }}</code></pre></dd>
<dt>Private Key of the Service Provider</dt>
<dd><pre><code>{{ "".join(service.config["sp_key"].strip().split("\n")[1:-1]) }}</code></pre></dd>
<dt>Identifier of the IDP entity</dt>
<dd><code>{{ url_for("sso.saml_metadata", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt>URL target of the IDP where the SP will send the Authentication Request Message</dt>
<dd><code>{{ url_for("sso.saml_redirect", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt>URL location of the IDP where the SP will send the SLO request</dt>
<dd><code>{{ service.config["application_uri"] }}</code></dd>
<dt>Public X.509 certificat of the IDP</dt>
<dd><pre><code>{{ "".join(service.config["idp_cert"].strip().split("\n")[1:-1]) }}</code></pre></dd>
</dl>
{% include "application_saml.html" %}
<h3>Detailed OpenID Connect settings</h3>
<dt>{% trans %}Authorization endpoint{% endtrans %}</dt>
<dd><pre>{{ url_for("sso.oidc_authorize", service_uuid=service.uuid, _external=True) }}</pre></dd>
<dt>{% trans %}Token endpoint{% endtrans %}</dt>
<dd><pre>{{ url_for("sso.oidc_token", service_uuid=service.uuid, _external=True) }}</pre></dd>
<dt>{% trans %}Userinfo endpoint{% endtrans %}</dt>
<dd><pre>{{ url_for("sso.oidc_userinfo", service_uuid=service.uuid, _external=True) }}</pre></dd>
<dt>{% trans %}Client ID{% endtrans %}</dt>
<dd><pre>{{ service.config["client_id"] }}</pre></dd>
<dt>{% trans %}Client secret{% endtrans %}</dt>
<dd><pre>{{ service.config["client_secret"] }}</dd>
<h4>Detailed OpenID Connect settings</h4>
<dl>
<!-- WARNING: do not split those tags into multiple lines, it would break auth tests -->
<dt id="metadata">{% trans %}OIDC discovery endpoint{% endtrans %}</dt>
<dd aria-labelledby="metadata"><code>{{ url_for("sso.oidc_discovery", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt id="authorization">{% trans %}Authorization endpoint{% endtrans %}</dt>
<dd aria-labelledby="authorization"><code>{{ url_for("sso.oidc_authorize", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt id="token">{% trans %}Token endpoint{% endtrans %}</dt>
<dd aria-labelledby="token"><code>{{ url_for("sso.oidc_token", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt id="userinfo">{% trans %}Userinfo endpoint{% endtrans %}</dt>
<dd aria-labelledby="userinfo"><code>{{ url_for("sso.oidc_userinfo", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt id="clientid">{% trans %}Client ID{% endtrans %}</dt>
<dd aria-labelledby="clientid"><code>{{ service.config["client_id"] }}</code></dd>
<dt id="secret">{% trans %}Client secret{% endtrans %}</dt>
<dd aria-labelledby="secret"><code>{{ service.config["client_secret"] }}</code></dd>
</dl>
<h3>Setting up PeerTube</h3>
<p>PeerTube supports OIDC authentication using the official <i>auth-openid-connect</i> plugin: https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-openid-connect.</p>
<p>Once the plugin is install, to configure OIDC authentication, you should fill the following settings.</p>
<dt>Discover URL</dt>
<dd><pre>{{ url_for("sso.oidc_discovery", service_uuid=service.uuid, _external=True) }}</pre></dd>
<dt>Client ID</dt>
<dd><pre>{{ service.config["client_id"] }}</pre></dd>
<dt>Client secret</dt>
<dd><pre>{{ service.config["client_secret"] }}</pre></dd>
<dt>Scope</dt>
<dd><pre>openid email profile</pre></dd>
<dt>Username property</dt>
<dd><pre>preferred_username</pre></dd>
<dt>Email property</dt>
<dd><pre>email</pre></dd>
{% include "application_oidc.html" %}
<p>PeerTube supports OIDC authentication using the official
<a href="https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-openid-connect">
auth-openid-connect</a>.</p>
<p>Once the plugin is installed, to configure OIDC authentication, you should fill the following settings:</p>
<dl>
<dt>Discover URL</dt>
<dd><code>{{ url_for("sso.oidc_discovery", service_uuid=service.uuid, _external=True) }}</code></dd>
<dt>Client ID</dt>
<dd><code>{{ service.config["client_id"] }}</code></dd>
<dt>Client secret</dt>
<dd><code>{{ service.config["client_secret"] }}</code></dd>
<dt>Scope</dt>
<dd><code>openid email profile</code></dd>
<dt>Username property</dt>
<dd><code>preferred_username</code></dd>
<dt>Email property</dt>
<dd><code>email</code></dd>
</dl>
{% include "application_oidc.html" %}