Client forms

This commit is contained in:
Éloi Rivard 2020-08-17 15:49:48 +02:00
parent 3a1284880a
commit d75fcb163b
18 changed files with 581 additions and 335 deletions

4
app.py
View file

@ -1,4 +0,0 @@
from web.app import create_app
app = create_app()

View file

@ -9,6 +9,7 @@ RUN pip install --requirement /app/requirements.txt
WORKDIR /app
USER oauthserver
ENV FLASK_APP=web
ENV FLASK_ENV=development
ENV AUTHLIB_INSECURE_TRANSPORT=1

View file

@ -122,6 +122,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.15 NAME 'oauthClientName'
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.16 NAME 'oauthClientContact'
@ -130,6 +131,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.16 NAME 'oauthClientContact'
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.17 NAME 'oauthClientURI'
@ -138,6 +140,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.17 NAME 'oauthClientURI'
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.18 NAME 'oauthLogoURI'
@ -155,6 +158,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.19 NAME 'oauthTermsOfServiceURI'
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.20 NAME 'oauthPolicyURI'
@ -163,6 +167,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.20 NAME 'oauthPolicyURI'
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.21 NAME 'oauthJWKURI'

View file

@ -1,5 +1,6 @@
authlib
flask
flask - babel
python - ldap
flask-babel
flask-wtf
python-ldap
toml

View file

@ -0,0 +1,68 @@
import ldap
import os
import toml
from . import routes, clients
from flask import Flask, g, request
from flask_babel import Babel
from .oauth2utils import config_oauth
def create_app(config=None):
app = Flask(__name__)
app.config.from_mapping(
{"OAUTH2_REFRESH_TOKEN_GENERATOR": True,}
)
app.config.from_mapping(toml.load(os.environ.get("CONFIG", "config.toml")))
app.url_map.strict_slashes = False
setup_app(app)
return app
def setup_app(app):
@app.before_request
def before_request():
g.ldap = ldap.initialize(app.config["LDAP"]["URI"])
g.ldap.simple_bind_s(
app.config["LDAP"]["BIND_USER"], app.config["LDAP"]["BIND_PW"]
)
@app.after_request
def after_request(response):
if "ldap" in g:
g.ldap.unbind_s()
return response
config_oauth(app)
app.register_blueprint(routes.bp)
app.register_blueprint(clients.bp, url_prefix="/client")
babel = Babel(app)
@app.context_processor
def global_processor():
return {
"logo_url": app.config.get("LOGO"),
"website_name": app.config.get("NAME"),
}
@babel.localeselector
def get_locale():
user = getattr(g, "user", None)
if user is not None:
return user.locale
if app.config.get("LANGUAGE"):
return app.config.get("LANGUAGE")
return request.accept_languages.best_match(["fr", "en"])
@babel.timezoneselector
def get_timezone():
user = getattr(g, "user", None)
if user is not None:
return user.timezone

View file

@ -1,67 +0,0 @@
import ldap
import os
import toml
from flask import Flask, g, request
from flask_babel import Babel
from .oauth2 import config_oauth
from .routes import bp
def create_app(config=None):
app = Flask(__name__)
app.config.from_mapping(
{"OAUTH2_REFRESH_TOKEN_GENERATOR": True,}
)
app.config.from_mapping(toml.load(os.environ.get("CONFIG", "config.toml")))
app.url_map.strict_slashes = False
setup_app(app)
return app
def setup_app(app):
@app.before_request
def before_request():
g.ldap = ldap.initialize(app.config["LDAP"]["URI"])
g.ldap.simple_bind_s(
app.config["LDAP"]["BIND_USER"], app.config["LDAP"]["BIND_PW"]
)
@app.after_request
def after_request(response):
if "ldap" in g:
g.ldap.unbind_s()
return response
config_oauth(app)
app.register_blueprint(bp, url_prefix="")
babel = Babel(app)
@app.context_processor
def global_processor():
return {
"logo_url": app.config.get("LOGO"),
"website_name": app.config.get("NAME"),
}
@babel.localeselector
def get_locale():
user = getattr(g, "user", None)
if user is not None:
return user.locale
if app.config.get("LANGUAGE"):
return app.config.get("LANGUAGE")
return request.accept_languages.best_match(["fr", "en"])
@babel.timezoneselector
def get_timezone():
user = getattr(g, "user", None)
if user is not None:
return user.timezone

195
web/clients.py Normal file
View file

@ -0,0 +1,195 @@
import datetime
import wtforms
import wtforms.fields.html5
from flask import Blueprint, render_template, request, flash, redirect, url_for
from flask_wtf import FlaskForm
from flask_babel import gettext
from werkzeug.security import gen_salt
from .models import Client
bp = Blueprint(__name__, "clients")
@bp.route("/")
def index():
clients = Client.filter()
return render_template("client_list.html", clients=clients)
class ClientAdd(FlaskForm):
oauthClientName = wtforms.TextField(
gettext("Name"),
validators=[wtforms.validators.DataRequired()],
render_kw={"placeholder": "Client Name"},
)
oauthClientContact = wtforms.fields.html5.EmailField(
gettext("Contact"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": "admin@mydomain.tld"},
)
oauthClientURI = wtforms.fields.html5.URLField(
gettext("URI"),
validators=[wtforms.validators.DataRequired()],
render_kw={"placeholder": "https://mydomain.tld"},
)
oauthRedirectURI = wtforms.fields.html5.URLField(
gettext("Redirect URIs"),
validators=[wtforms.validators.DataRequired()],
render_kw={"placeholder": "https://mydomain.tld/callback"},
)
oauthGrantType = wtforms.SelectMultipleField(
gettext("Grant types"),
validators=[wtforms.validators.DataRequired()],
choices=[
("password", "password"),
("authorization_code", "authorization_code"),
],
default=["authorization_code"],
)
oauthScope = wtforms.TextField(
gettext("Scope"),
validators=[wtforms.validators.Optional()],
default="openid profile",
render_kw={"placeholder": "openid profile"},
)
oauthResponseType = wtforms.SelectMultipleField(
gettext("Response types"),
validators=[wtforms.validators.DataRequired()],
choices=[("code", "code")],
default=["code"],
)
oauthTokenEndpointAuthMethod = wtforms.SelectField(
gettext("Token Endpoint Auth Method"),
validators=[wtforms.validators.DataRequired()],
choices=[
("client_secret_basic", "client_secret_basic"),
("client_secret_post", "client_secret_post"),
("none", "none"),
],
default="client_secret_basic",
)
oauthLogoURI = wtforms.fields.html5.URLField(
gettext("Logo URI"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": "https://mydomain.tld/logo.png"},
)
oauthTermsOfServiceURI = wtforms.fields.html5.URLField(
gettext("Terms of service URI"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": "https://mydomain.tld/tos.html"},
)
oauthPolicyURI = wtforms.fields.html5.URLField(
gettext("Policy URI"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": "https://mydomain.tld/policy.html"},
)
oauthSoftwareID = wtforms.TextField(
gettext("Software ID"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": "xyz"},
)
oauthSoftwareVersion = wtforms.TextField(
gettext("Software Version"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": "1.0"},
)
oauthJWK = wtforms.TextField(
gettext("JWK"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": ""},
)
oauthJWKURI = wtforms.fields.html5.URLField(
gettext("JKW URI"),
validators=[wtforms.validators.Optional()],
render_kw={"placeholder": ""},
)
@bp.route("/add", methods=["GET", "POST"])
def add():
form = ClientAdd(request.form or None)
if not request.form:
return render_template("client_add.html", form=form)
if not form.validate():
flash(
gettext("The client has not been added. Please check your information."),
"error",
)
return render_template("client_add.html", form=form)
client_id = gen_salt(24)
client_id_issued_at = datetime.datetime.now().strftime("%Y%m%d%H%M%SZ")
client = Client(
oauthClientID=client_id,
oauthIssueDate=client_id_issued_at,
oauthClientName=form["oauthClientName"].data,
oauthClientContact=form["oauthClientContact"].data,
oauthClientURI=form["oauthClientURI"].data,
oauthGrantType=form["oauthGrantType"].data,
oauthRedirectURI=[form["oauthRedirectURI"].data],
oauthResponseType=form["oauthResponseType"].data,
oauthScope=form["oauthScope"].data.split(" "),
oauthTokenEndpointAuthMethod=form["oauthTokenEndpointAuthMethod"].data,
oauthLogoURI=form["oauthLogoURI"].data,
oauthTermsOfServiceURI=form["oauthTermsOfServiceURI"].data,
oauthPolicyURI=form["oauthPolicyURI"].data,
oauthSoftwareID=form["oauthSoftwareID"].data,
oauthSoftwareVersion=form["oauthSoftwareVersion"].data,
oauthJWK=form["oauthJWK"].data,
oauthJWKURI=form["oauthJWKURI"].data,
oauthClientSecret=""
if form["oauthTokenEndpointAuthMethod"].data == "none"
else gen_salt(48),
)
client.save()
flash(
gettext("The client has been created."), "success",
)
return redirect(url_for("web.clients.edit", client_id=client_id))
@bp.route("/edit/<client_id>", methods=["GET", "POST"])
def edit(client_id):
client = Client.get(client_id)
data = dict(client)
data["oauthScope"] = " ".join(data["oauthScope"])
data["oauthRedirectURI"] = data["oauthRedirectURI"][0]
form = ClientAdd(request.form or None, data=data, client=client)
if not request.form:
return render_template("client_edit.html", form=form, client=client)
if not form.validate():
flash(
gettext("The client has not been edited. Please check your information."),
"error",
)
else:
client.update(
oauthClientName=form["oauthClientName"].data,
oauthClientContact=form["oauthClientContact"].data,
oauthClientURI=form["oauthClientURI"].data,
oauthGrantType=form["oauthGrantType"].data,
oauthRedirectURI=[form["oauthRedirectURI"].data],
oauthResponseType=form["oauthResponseType"].data,
oauthScope=form["oauthScope"].data.split(" "),
oauthTokenEndpointAuthMethod=form["oauthTokenEndpointAuthMethod"].data,
oauthLogoURI=form["oauthLogoURI"].data,
oauthTermsOfServiceURI=form["oauthTermsOfServiceURI"].data,
oauthPolicyURI=form["oauthPolicyURI"].data,
oauthSoftwareID=form["oauthSoftwareID"].data,
oauthSoftwareVersion=form["oauthSoftwareVersion"].data,
oauthJWK=form["oauthJWK"].data,
oauthJWKURI=form["oauthJWKURI"].data,
)
client.save()
flash(
gettext("The client has been edited."), "success",
)
return render_template("client_edit.html", form=form, client=client)

150
web/ldaputils.py Normal file
View file

@ -0,0 +1,150 @@
import ldap
from flask import g
class LDAPObjectHelper:
_object_class_by_name = None
_attribute_type_by_name = None
may = None
must = None
base = None
id = None
def __init__(self, dn=None, **kwargs):
self.attrs = {}
for k, v in kwargs.items():
self.attrs[k] = [v] if not isinstance(v, list) else v
self.attrs.setdefault("objectClass", self.objectClass)
by_name = self.ocs_by_name()
ocs = [by_name[name] for name in self.objectClass]
self.may = []
self.must = []
for oc in ocs:
self.may.extend(oc.may)
self.must.extend(oc.must)
def keys(self):
return self.must + self.may
def __getitem__(self, item):
return getattr(self, item)
def update(self, **kwargs):
for k, v in kwargs.items():
self.__setattr__(k, v)
@property
def dn(self):
if not self.id in self.attrs:
return None
return f"{self.id}={self.attrs[self.id][0]},{self.base}"
@classmethod
def ocs_by_name(cls):
if cls._object_class_by_name:
return cls._object_class_by_name
res = g.ldap.search_s(
"cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]
)
subschema_entry = res[0]
subschema_subentry = ldap.cidict.cidict(subschema_entry[1])
subschema = ldap.schema.SubSchema(subschema_subentry)
object_class_oids = subschema.listall(ldap.schema.models.ObjectClass)
cls._object_class_by_name = {}
for oid in object_class_oids:
oc = subschema.get_obj(ldap.schema.models.ObjectClass, oid)
for name in oc.names:
cls._object_class_by_name[name] = oc
return cls._object_class_by_name
@classmethod
def attr_type_by_name(cls):
if cls._attribute_type_by_name:
return cls._attribute_type_by_name
res = g.ldap.search_s(
"cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]
)
subschema_entry = res[0]
subschema_subentry = ldap.cidict.cidict(subschema_entry[1])
subschema = ldap.schema.SubSchema(subschema_subentry)
attribute_type_oids = subschema.listall(ldap.schema.models.AttributeType)
cls._attribute_type_by_name = {}
for oid in attribute_type_oids:
oc = subschema.get_obj(ldap.schema.models.AttributeType, oid)
for name in oc.names:
cls._attribute_type_by_name[name] = oc
return cls._attribute_type_by_name
def save(self):
try:
match = bool(g.ldap.search_s(self.dn, ldap.SCOPE_SUBTREE))
except ldap.NO_SUCH_OBJECT:
match = False
if match:
attributes = [
(ldap.MOD_REPLACE, k, [elt.encode("utf-8") for elt in v])
for k, v in self.attrs.items()
]
g.ldap.modify_s(self.dn, attributes)
else:
attributes = [
(k, [elt.encode("utf-8") for elt in v]) for k, v in self.attrs.items()
]
g.ldap.add_s(self.dn, attributes)
@classmethod
def get(cls, dn):
if "=" not in dn:
dn = f"{cls.id}={dn},{cls.base}"
result = g.ldap.search_s(dn, ldap.SCOPE_SUBTREE)
if not result:
return None
o = cls(
**{k: [elt.decode("utf-8") for elt in v] for k, v in result[0][1].items()}
)
return o
@classmethod
def filter(cls, base=None, **kwargs):
class_filter = "".join([f"(objectClass={oc})" for oc in cls.objectClass])
arg_filter = "".join(f"({k}={v})" for k, v in kwargs.items())
ldapfilter = f"(&{class_filter}{arg_filter})"
result = g.ldap.search_s(base or cls.base, ldap.SCOPE_SUBTREE, ldapfilter)
return [
cls(**{k: [elt.decode("utf-8") for elt in v] for k, v in args.items()},)
for _, args in result
]
def __getattr__(self, name):
if (not self.may or name not in self.may) and (
not self.must or name not in self.must
):
return super().__getattribute__(name)
if (
not self.attr_type_by_name()
or not self.attr_type_by_name()[name].single_value
):
return self.attrs.get(name, [])
return self.attrs.get(name, [None])[0]
def __setattr__(self, name, value):
super().__setattr__(name, value)
if (self.may and name in self.may) or (self.must and name in self.must):
if self.attr_type_by_name()[name].single_value:
self.attrs[name] = [value]
else:
self.attrs[name] = value

View file

@ -1,152 +1,12 @@
import ldap
import time
import datetime
from flask import g
from authlib.common.encoding import json_loads, json_dumps
from authlib.oauth2.rfc6749 import (
ClientMixin,
TokenMixin,
AuthorizationCodeMixin,
)
class LDAPObjectHelper:
_object_class_by_name = None
_attribute_type_by_name = None
may = None
must = None
base = None
id = None
def __init__(self, dn=None, **kwargs):
self.attrs = {}
for k, v in kwargs.items():
self.attrs[k] = [v] if not isinstance(v, list) else v
self.attrs.setdefault("objectClass", self.objectClass)
by_name = self.ocs_by_name()
ocs = [by_name[name] for name in self.objectClass]
self.may = []
self.must = []
for oc in ocs:
self.may.extend(oc.may)
self.must.extend(oc.must)
@property
def dn(self):
if not self.id in self.attrs:
return None
return f"{self.id}={self.attrs[self.id][0]},{self.base}"
@classmethod
def ocs_by_name(cls):
if cls._object_class_by_name:
return cls._object_class_by_name
res = g.ldap.search_s(
"cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]
)
subschema_entry = res[0]
subschema_subentry = ldap.cidict.cidict(subschema_entry[1])
subschema = ldap.schema.SubSchema(subschema_subentry)
object_class_oids = subschema.listall(ldap.schema.models.ObjectClass)
cls._object_class_by_name = {}
for oid in object_class_oids:
oc = subschema.get_obj(ldap.schema.models.ObjectClass, oid)
for name in oc.names:
cls._object_class_by_name[name] = oc
return cls._object_class_by_name
@classmethod
def attr_type_by_name(cls):
if cls._attribute_type_by_name:
return cls._attribute_type_by_name
res = g.ldap.search_s(
"cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]
)
subschema_entry = res[0]
subschema_subentry = ldap.cidict.cidict(subschema_entry[1])
subschema = ldap.schema.SubSchema(subschema_subentry)
attribute_type_oids = subschema.listall(ldap.schema.models.AttributeType)
cls._attribute_type_by_name = {}
for oid in attribute_type_oids:
oc = subschema.get_obj(ldap.schema.models.AttributeType, oid)
for name in oc.names:
cls._attribute_type_by_name[name] = oc
return cls._attribute_type_by_name
def save(self):
try:
match = bool(g.ldap.search_s(self.dn, ldap.SCOPE_SUBTREE))
except ldap.NO_SUCH_OBJECT:
match = False
if match:
attributes = [
(ldap.MOD_REPLACE, k, [elt.encode("utf-8") for elt in v])
for k, v in self.attrs.items()
]
g.ldap.modify_s(self.dn, attributes)
else:
attributes = [
(k, [elt.encode("utf-8") for elt in v]) for k, v in self.attrs.items()
]
g.ldap.add_s(self.dn, attributes)
@classmethod
def get(cls, dn):
if "=" not in dn:
dn = f"{cls.id}={dn},{cls.base}"
result = g.ldap.search_s(dn, ldap.SCOPE_SUBTREE)
if not result:
return None
o = cls(
**{k: [elt.decode("utf-8") for elt in v] for k, v in result[0][1].items()}
)
return o
@classmethod
def filter(cls, base=None, **kwargs):
class_filter = "".join([f"(objectClass={oc})" for oc in cls.objectClass])
arg_filter = "".join(f"({k}={v})" for k, v in kwargs.items())
ldapfilter = f"(&{class_filter}{arg_filter})"
result = g.ldap.search_s(base or cls.base, ldap.SCOPE_SUBTREE, ldapfilter)
return [
cls(**{k: [elt.decode("utf-8") for elt in v] for k, v in args.items()},)
for _, args in result
]
def __getattr__(self, name):
if (not self.may or name not in self.may) and (
not self.must or name not in self.must
):
return super().__getattribute__(name)
if (
not self.attr_type_by_name()
or not self.attr_type_by_name()[name].single_value
):
return self.attrs.get(name, [])
return self.attrs.get(name, [None])[0]
def __setattr__(self, name, value):
super().__setattr__(name, value)
if not isinstance(value, list):
value = [value]
if (self.may and name in self.may) or (self.must and name in self.must):
if self.attr_type_by_name()[name].single_value:
self.attrs[name] = [value]
else:
self.attrs[name] = value
from .ldaputils import LDAPObjectHelper
class User(LDAPObjectHelper):
@ -166,6 +26,10 @@ class Client(LDAPObjectHelper, ClientMixin):
base = "ou=clients,dc=mydomain,dc=tld"
id = "oauthClientID"
@property
def issue_date(self):
return datetime.datetime.strptime(self.oauthIssueDate, "%Y%m%d%H%M%SZ")
def get_client_id(self):
return self.oauthClientID

View file

@ -1,10 +1,8 @@
import datetime
from flask import Blueprint, request, session
from flask import render_template, redirect, jsonify
from werkzeug.security import gen_salt
from authlib.oauth2 import OAuth2Error
from .models import User, Client
from .oauth2 import authorization, require_oauth
from .oauth2utils import authorization, require_oauth
bp = Blueprint(__name__, "home")
@ -38,40 +36,6 @@ def home():
return render_template("home.html", user=user, clients=clients)
def split_by_crlf(s):
return [v for v in s.splitlines() if v]
@bp.route("/create_client", methods=("GET", "POST"))
def create_client():
user = current_user()
if not user:
return redirect("/")
if request.method == "GET":
return render_template("create_client.html")
form = request.form
client_id = gen_salt(24)
client_id_issued_at = datetime.datetime.now().strftime("%Y%m%d%H%M%SZ")
client = Client(
oauthClientID=client_id,
oauthIssueDate=client_id_issued_at,
oauthClientName=form["client_name"],
oauthClientURI=form["client_uri"],
oauthGrantType=split_by_crlf(form["grant_type"]),
oauthRedirectURI=split_by_crlf(form["redirect_uri"]),
oauthResponseType=split_by_crlf(form["response_type"]),
oauthScope=form["scope"],
oauthTokenEndpointAuthMethod=form["token_endpoint_auth_method"],
oauthClientSecret=""
if form["token_endpoint_auth_method"] == "none"
else gen_salt(48),
)
client.save()
return redirect("/")
@bp.route("/oauth/authorize", methods=["GET", "POST"])
def authorize():
user = current_user()

View file

@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{% block title %}Accueil{% endblock %}</title>
<title>{% block title %}{% trans %}OpenID Connect LDAP Bridge{% endtrans %}{% endblock %}</title>
<link href="/static/fomanticui/semantic.min.css" rel="stylesheet">
<link href="/static/css/base.css" rel="stylesheet">
{% block style %}{% endblock %}
@ -28,6 +28,18 @@
</a>
</div>
{% endif %}
<a class="item" href="{{ url_for('web.clients.index') }}">
<i class="plug icon"></i>
{% trans %}Clients{% endtrans %}
</a>
<a class="item" href="/">
<i class="key icon"></i>
{% trans %}Tokens{% endtrans %}
</a>
<a class="item" href="/">
<i class="user secret icon"></i>
{% trans %}Codes{% endtrans %}
</a>
</nav>
{% endblock %}
<div class="content">

View file

@ -0,0 +1,20 @@
{% extends 'base.html' %}
{% import 'fomanticui.j2' as sui %}
{% block content %}
<div class="loginform">
<h3 class="ui top attached header">
{% trans %}Add a client{% endtrans %}
</h3>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="ui attached message {{ category }}">
{{ message }}
</div>
{% endfor %}
{% endwith %}
<div class="ui attached clearing segment">
{{ sui.render_form(form, _("Confirm")) }}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,35 @@
{% extends 'base.html' %}
{% import 'fomanticui.j2' as sui %}
{% block content %}
<div class="loginform">
<h3 class="ui top attached header">
{% trans %}Edit a client{% endtrans %}
</h3>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="ui attached message {{ category }}">
{{ message }}
</div>
{% endfor %}
{% endwith %}
<div class="ui attached clearing segment">
<div class="ui form">
<div class="field">
<label>{% trans %}ID{% endtrans %}</label>
<input type="text" value="{{ client.oauthClientID }}" readonly>
</div>
<div class="field">
<label>{% trans %}Secret{% endtrans %}</label>
<input type="text" value="{{ client.oauthClientSecret }}" readonly>
</div>
<div class="field">
<label>{% trans %}Issued at{% endtrans %}</label>
<input type="text" value="{{ client.issue_date }}" readonly>
</div>
</div>
{{ sui.render_form(form, _("Confirm")) }}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,35 @@
{% extends 'base.html' %}
{% block style %}
<link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet">
<link href="/static/datatables/dataTables.semanticui.min.css" rel="stylesheet">
{% endblock %}
{% block script %}
<script src="/static/datatables/jquery.dataTables.min.js"></script>
<script src="/static/datatables/dataTables.semanticui.min.js"></script>
<script src="/static/js/users.js"></script>
{% endblock %}
{% block content %}
<div class="ui segment">
<a class="ui primary button" href="{{ url_for('web.clients.add') }}">{% trans %}Add client{% endtrans %}</a>
</div>
<table class="ui table">
<thead>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}URL{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th>
</thead>
{% for client in clients %}
<tr>
<td><a href="{{ url_for('web.clients.edit', client_id=client.oauthClientID) }}">{{ client.oauthClientName }}</a></td>
<td><a href="{{ client.oauthClientURI }}">{{ client.oauthClientURI }}</a></td>
<td>{{ client.issue_date }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,48 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="ui segment">
<style>
label, label > span { display: block; }
label { margin: 15px 0; }
</style>
<form action="" method="post">
<label>
<span>Client Name</span>
<input type="text" name="client_name">
</label>
<label>
<span>Client URI</span>
<input type="url" name="client_uri">
</label>
<label>
<span>Allowed Scope</span>
<input type="text" name="scope">
</label>
<label>
<span>Redirect URIs</span>
<textarea name="redirect_uri" cols="30" rows="10"></textarea>
</label>
<label>
<span>Allowed Grant Types</span>
<textarea name="grant_type" cols="30" rows="10"></textarea>
</label>
<label>
<span>Allowed Response Types</span>
<textarea name="response_type" cols="30" rows="10"></textarea>
</label>
<label>
<span>Token Endpoint Auth Method</span>
<select name="token_endpoint_auth_method">
<option value="client_secret_basic">client_secret_basic</option>
<option value="client_secret_post">client_secret_post</option>
<option value="none">none</option>
</select>
</label>
<button>Submit</button>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,49 @@
{% macro render_field(field, label_visible=true) -%}
<div class="field {{ kwargs.pop('class_', '') }} {% if field.errors %} error{% endif %}">
{% if (field.type != 'HiddenField' and field.type !='CSRFTokenField') and label_visible %}
{{ field.label() }}
{% endif %}
{% if field.errors %}
{% for error in field.errors %}
<div class="ui error message">
<p>{{ error }}</p>
</div>
{% endfor %}
{% endif %}
{% if field.type in ("SelectField", "SelectMultipleField") %}
{{ field(class_="ui dropdown multiple", **kwargs) }}
{% else %}
{{ field(**kwargs) }}
{% endif %}
</div>
{%- endmacro %}
{% macro render_form(
form,
action_text='submit',
class_='',
btn_class='ui right floated primary button',
action=none,
id=none) -%}
<form method="POST"
id="{{ id or form.__class__.__name__|lower }}"
action="{{ action or form.action }}"
role="form"
enctype="multipart/form-data"
class="ui form {{ class_ }}"
>
{{ form.hidden_tag() if form.hidden_tag }}
{% if caller %}
{{ caller() }}
{% else %}
{% for field in form %}
{{ render_field(field) }}
{% endfor %}
{% endif %}
{% if action_text %}
<button type="submit" class="{{ btn_class }}">{{ action_text }}</button>
{% endif %}
</form>
{%- endmacro %}

View file

@ -2,40 +2,6 @@
{% block content %}
<div class="ui segment">
{% if user %}
<style>pre{white-space:wrap}</style>
<div>{{ _('Logged in as') }} <strong>{{user}}</strong> (<a href="{{ url_for('.logout') }}">Log Out</a>)</div>
{% for client in clients %}
<strong>Client</strong>
<ul>
{%- for key in client.must -%}
{%- if key in client.attrs and client.attrs[key] -%}
{%- for value in client.attrs[key] -%}
<li><strong>{{ key }}: </strong>{{ value }}</li>
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{%- for key in client.may -%}
{%- if key in client.attrs and client.attrs[key] -%}
{%- for value in client.attrs[key] -%}
<li><strong>{{ key }}: </strong>{{ value }}</li>
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
</ul>
<hr>
{% endfor %}
<br><a href="{{ url_for('.create_client') }}">{% trans %}Create Client{% endtrans %}</a>
{% else %}
<form action="" method="post">
<input type="text" name="username" placeholder="username">
<button type="submit">Login / Signup</button>
</form>
{% endif %}
Welcome
</div>
{% endblock %}