add more registration stuff, admin interface
This commit is contained in:
parent
c1c8876b63
commit
67b69104d6
|
@ -47,6 +47,54 @@ window.$(document).ready(function () {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.admin = {
|
||||||
|
registration: {
|
||||||
|
delete: function(href, registration_id, username) {
|
||||||
|
var dialog = new ConfirmDialog('Reject user registration', `Are you sure to reject the registration request from "${username}"?`);
|
||||||
|
dialog.show().then(()=>{
|
||||||
|
fetch(href, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
accept: function(href, registration_id, username) {
|
||||||
|
var dialog = new ConfirmDialog('Accept user registration', `Are you sure to accept the registration request from "${username}"?`);
|
||||||
|
dialog.show().then(()=>{
|
||||||
|
fetch(href, {
|
||||||
|
method: 'PUT'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.auth = {
|
||||||
|
sign_up: {
|
||||||
|
submit: function(form) {
|
||||||
|
SimpleFormSubmit.submitForm(form.action, form)
|
||||||
|
.then(response =>{
|
||||||
|
response.json().then(function(data) {
|
||||||
|
if (data.errors) {
|
||||||
|
var msg ='<ul>';
|
||||||
|
for( var field in data.errors) {
|
||||||
|
msg += `<li>${field}: ${data.errors[field]}</li>`;
|
||||||
|
}
|
||||||
|
msg += '</ul>';
|
||||||
|
new Dialog('Registration Error', `Error Happend: ${msg}`).show()
|
||||||
|
} else {
|
||||||
|
new Dialog('Registration successfully', 'Wait until an administrator has aproved your account').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.totp = {
|
window.totp = {
|
||||||
init_list: function(){
|
init_list: function(){
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
nav.sidebar {
|
||||||
|
top: 44px;
|
||||||
|
}
|
||||||
|
|
10
import.py
10
import.py
|
@ -1,9 +1,13 @@
|
||||||
import logging
|
import logging
|
||||||
from lenticular_cloud.app import oidc_provider_init_app
|
from lenticular_cloud.app import init_app
|
||||||
|
from lenticular_cloud.model import User
|
||||||
|
|
||||||
name = 'oidc_provider'
|
name = 'oidc_provider'
|
||||||
app = oidc_provider_init_app(name)
|
app = init_app(name)
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
with app.context():
|
with app.app_context():
|
||||||
|
for ldap_user in User.query_().all():
|
||||||
|
user = User.query.filter_by(username=str(ldap_user.username)).first()
|
||||||
|
print(user)
|
||||||
|
|
||||||
|
|
|
@ -54,12 +54,13 @@ def init_app(name=None):
|
||||||
hydra_client = hydra.ApiClient(hydra_config)
|
hydra_client = hydra.ApiClient(hydra_config)
|
||||||
app.hydra_api = hydra.AdminApi(hydra_client)
|
app.hydra_api = hydra.AdminApi(hydra_client)
|
||||||
|
|
||||||
from .views import auth_views, frontend_views, init_login_manager, api_views, pki_views
|
from .views import auth_views, frontend_views, init_login_manager, api_views, pki_views, admin_views
|
||||||
init_login_manager(app)
|
init_login_manager(app)
|
||||||
app.register_blueprint(auth_views)
|
app.register_blueprint(auth_views)
|
||||||
app.register_blueprint(frontend_views)
|
app.register_blueprint(frontend_views)
|
||||||
app.register_blueprint(api_views)
|
app.register_blueprint(api_views)
|
||||||
app.register_blueprint(pki_views)
|
app.register_blueprint(pki_views)
|
||||||
|
app.register_blueprint(admin_views)
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def befor_request():
|
def befor_request():
|
||||||
|
|
|
@ -6,7 +6,7 @@ from wtforms import StringField, SubmitField, TextField, \
|
||||||
SelectField, Form as NoCsrfForm, SelectMultipleField
|
SelectField, Form as NoCsrfForm, SelectMultipleField
|
||||||
from wtforms.fields.html5 import EmailField
|
from wtforms.fields.html5 import EmailField
|
||||||
from wtforms.widgets.html5 import NumberInput, DateInput
|
from wtforms.widgets.html5 import NumberInput, DateInput
|
||||||
from wtforms.validators import DataRequired, NumberRange, Optional, NoneOf, Length
|
from wtforms.validators import DataRequired, NumberRange, Optional, NoneOf, Length, Regexp
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,12 +33,18 @@ class Fido2Form(FlaskForm):
|
||||||
class ConsentForm(FlaskForm):
|
class ConsentForm(FlaskForm):
|
||||||
# scopes = SelectMultipleField(gettext('scopes'))
|
# scopes = SelectMultipleField(gettext('scopes'))
|
||||||
# audiences = SelectMultipleField(gettext('audiences'))
|
# audiences = SelectMultipleField(gettext('audiences'))
|
||||||
remember = BooleanField(gettext('remember me'))
|
remember = BooleanField(gettext('remember'))
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
|
||||||
|
|
||||||
class RegistrationForm(FlaskForm):
|
class RegistrationForm(FlaskForm):
|
||||||
username = StringField(gettext('Username'), validators=[DataRequired()])
|
username = StringField(gettext('Username'), validators=[
|
||||||
password = PasswordField(gettext('Password'), validators=[DataRequired()])
|
DataRequired(),
|
||||||
alternative_email = EmailField(gettext('Alternative Email'))
|
Regexp('^[a-zA-Z0-9-.]+$', message=gettext('Only `a-z`, `A-Z`, `0-9`, `-.` is allowed for username'))
|
||||||
|
])
|
||||||
|
password = PasswordField(gettext('Password'), validators=[
|
||||||
|
DataRequired(),
|
||||||
|
Length(min=6)
|
||||||
|
])
|
||||||
|
alternative_email = EmailField(gettext('Alternative Email'), render_kw={"placeholder": "Optional"})
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
|
|
@ -28,6 +28,14 @@ base_dn = ''
|
||||||
db = SQLAlchemy() # type: SQLAlchemy
|
db = SQLAlchemy() # type: SQLAlchemy
|
||||||
|
|
||||||
|
|
||||||
|
class UserSignUp(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String, nullable=False)
|
||||||
|
password = db.Column(db.String, nullable=False)
|
||||||
|
alternative_email = db.Column(db.String)
|
||||||
|
created_at = db.Column(db.DateTime, nullable=False,
|
||||||
|
default=datetime.now)
|
||||||
|
|
||||||
|
|
||||||
class SecurityUser(UserMixin):
|
class SecurityUser(UserMixin):
|
||||||
|
|
||||||
|
@ -51,7 +59,7 @@ class EntryBase(db.Model):
|
||||||
__abstract__ = True # for sqlalchemy
|
__abstract__ = True # for sqlalchemy
|
||||||
|
|
||||||
_type = None # will get replaced by the local type
|
_type = None # will get replaced by the local type
|
||||||
_query_object = None # will get replaced by the local type
|
_ldap_query_object = None # will get replaced by the local type
|
||||||
_base_dn = LambdaStr(lambda: base_dn)
|
_base_dn = LambdaStr(lambda: base_dn)
|
||||||
|
|
||||||
# def __init__(self, ldap_object=None, **kwargs):
|
# def __init__(self, ldap_object=None, **kwargs):
|
||||||
|
@ -60,39 +68,45 @@ class EntryBase(db.Model):
|
||||||
# else:
|
# else:
|
||||||
# self._ldap_object = ldap_object
|
# self._ldap_object = ldap_object
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return str(self._ldap_object)
|
return str(self._ldap_object)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_object_def(cls):
|
def get_object_def(cls) -> ObjectDef:
|
||||||
return ObjectDef(cls.object_classes, ldap_conn)
|
return ObjectDef(cls.object_classes, ldap_conn)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_base(cls):
|
def get_entry_type(cls) -> EntryType:
|
||||||
|
return EntryType(cls.get_dn(), cls.object_classes, ldap_conn)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_base(cls) -> str:
|
||||||
return cls.base_dn.format(_base_dn=base_dn)
|
return cls.base_dn.format(_base_dn=base_dn)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_dn(cls) -> str:
|
||||||
|
return cls.dn.replace('{base_dn}', cls.get_base())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
if cls._type is None:
|
if cls._type is None:
|
||||||
cls._type = EntryType(cls.dn.replace('{base_dn}',cls.get_base()), cls.object_classes, ldap_conn)
|
cls._type = EntryType(cls.get_dn(), cls.object_classes, ldap_conn)
|
||||||
return cls._type
|
return cls._type
|
||||||
|
|
||||||
def commit(self):
|
def ldap_commit(self):
|
||||||
self._ldap_object.entry_commit_changes()
|
self._ldap_object.entry_commit_changes()
|
||||||
|
|
||||||
def add(self):
|
def ldap_add(self):
|
||||||
print(self._ldap_object.entry_attributes_as_dict)
|
|
||||||
ret = ldap_conn.add(
|
ret = ldap_conn.add(
|
||||||
self.dn, self.object_classes, self._ldap_object.entry_attributes_as_dict)
|
self.entry_dn, self.object_classes, self._ldap_object.entry_attributes_as_dict)
|
||||||
logger.debug(ret)
|
if not ret:
|
||||||
pass
|
raise Exception('ldap error')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_(cls):
|
def query_(cls):
|
||||||
if cls._query_object is None:
|
if cls._ldap_query_object is None:
|
||||||
cls._query_object = cls._query(cls)
|
cls._ldap_query_object = cls._query(cls)
|
||||||
return cls._query_object
|
return cls._ldap_query_object
|
||||||
|
|
||||||
|
|
||||||
class _query(object):
|
class _query(object):
|
||||||
def __init__(self, clazz):
|
def __init__(self, clazz):
|
||||||
|
@ -114,8 +128,6 @@ class EntryBase(db.Model):
|
||||||
return self._query(None)
|
return self._query(None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Service(object):
|
class Service(object):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
@ -213,16 +225,20 @@ class User(EntryBase):
|
||||||
id = db.Column(
|
id = db.Column(
|
||||||
db.String(length=36), primary_key=True, default=generate_uuid)
|
db.String(length=36), primary_key=True, default=generate_uuid)
|
||||||
username = db.Column(
|
username = db.Column(
|
||||||
db.String, unique=True)
|
db.String, unique=True, nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, nullable=False,
|
||||||
|
default=datetime.now)
|
||||||
|
modified_at = db.Column(db.DateTime, nullable=False,
|
||||||
|
default=datetime.now, onupdate=datetime.now)
|
||||||
|
last_login = db.Column(db.DateTime, nullable=True)
|
||||||
|
|
||||||
totps = db.relationship('Totp', back_populates='user')
|
totps = db.relationship('Totp', back_populates='user')
|
||||||
|
|
||||||
|
|
||||||
dn = "uid={uid},{base_dn}"
|
dn = "uid={uid},{base_dn}"
|
||||||
base_dn = "ou=users,{_base_dn}"
|
base_dn = "ou=users,{_base_dn}"
|
||||||
object_classes = ["top", "inetOrgPerson", "LenticularUser"]
|
object_classes = ["top", "inetOrgPerson", "LenticularUser"]
|
||||||
|
|
||||||
def __init__(self,**kwargs):
|
def __init__(self, **kwargs):
|
||||||
self._ldap_object = None
|
self._ldap_object = None
|
||||||
super(db.Model).__init__(**kwargs)
|
super(db.Model).__init__(**kwargs)
|
||||||
|
|
||||||
|
@ -236,6 +252,13 @@ class User(EntryBase):
|
||||||
def make_writeable(self):
|
def make_writeable(self):
|
||||||
self._ldap_object = self._ldap_object.entry_writable()
|
self._ldap_object = self._ldap_object.entry_writable()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def groups(self):
|
||||||
|
if self.username == 'tuxcoder':
|
||||||
|
return [Group(name='admin')]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_dn(self):
|
def entry_dn(self):
|
||||||
return self._ldap_object.entry_dn
|
return self._ldap_object.entry_dn
|
||||||
|
@ -274,7 +297,7 @@ class User(EntryBase):
|
||||||
self.make_writeable()
|
self.make_writeable()
|
||||||
password_hashed = crypt.crypt(password_new)
|
password_hashed = crypt.crypt(password_new)
|
||||||
self._ldap_object.userPassword = ('{CRYPT}' + password_hashed).encode()
|
self._ldap_object.userPassword = ('{CRYPT}' + password_hashed).encode()
|
||||||
self.commit()
|
self.ldap_commit()
|
||||||
|
|
||||||
class _query(EntryBase._query):
|
class _query(EntryBase._query):
|
||||||
|
|
||||||
|
@ -296,6 +319,21 @@ class User(EntryBase):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new(user_data: UserSignUp):
|
||||||
|
user = User()
|
||||||
|
user.username = user_data.username.lower()
|
||||||
|
domain = current_app.config['DOMAIN']
|
||||||
|
ldap_object = User.get_entry_type()(
|
||||||
|
uid=user_data.username.lower(),
|
||||||
|
sn=user_data.username,
|
||||||
|
cn=user_data.username,
|
||||||
|
userPassword='{CRYPT}' + user_data.password,
|
||||||
|
mail=f'{user_data.username}@{domain}')
|
||||||
|
user._ldap_object = ldap_object
|
||||||
|
user.ldap_add()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Totp(db.Model):
|
class Totp(db.Model):
|
||||||
|
@ -321,9 +359,6 @@ class Group(EntryBase):
|
||||||
|
|
||||||
fullname = AttrDef("cn")
|
fullname = AttrDef("cn")
|
||||||
|
|
||||||
|
|
||||||
class UserSignUp(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
username = db.Column(db.String, nullable=False)
|
name = db.Column(db.String(), nullable=False, unique=True)
|
||||||
password = db.Column(db.String, nullable=False)
|
|
||||||
alternative_email = db.Column(db.String)
|
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
from .auth import auth_views
|
from .auth import auth_views
|
||||||
from .frontend import frontend_views, init_login_manager
|
from .frontend import frontend_views, init_login_manager
|
||||||
|
from .admin import admin_views
|
||||||
from .api import api_views
|
from .api import api_views
|
||||||
from .pki import pki_views
|
from .pki import pki_views
|
||||||
|
|
66
lenticular_cloud/views/admin.py
Normal file
66
lenticular_cloud/views/admin.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import flask
|
||||||
|
from flask import Blueprint, redirect, request, url_for, render_template
|
||||||
|
from flask import current_app, session
|
||||||
|
from flask import jsonify
|
||||||
|
from flask_login import current_user, logout_user
|
||||||
|
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
|
||||||
|
from ..model import db, User, UserSignUp
|
||||||
|
|
||||||
|
|
||||||
|
admin_views = Blueprint('admin', __name__, url_prefix='/admin')
|
||||||
|
|
||||||
|
|
||||||
|
def before_request():
|
||||||
|
try:
|
||||||
|
resp = current_app.oauth.session.get('/userinfo')
|
||||||
|
data = resp.json()
|
||||||
|
if not current_user.is_authenticated or resp.status_code is not 200:
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('oauth.login'))
|
||||||
|
if 'admin' not in data['groups']:
|
||||||
|
return 'Not an admin', 403
|
||||||
|
except TokenExpiredError:
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('oauth.login'))
|
||||||
|
|
||||||
|
|
||||||
|
admin_views.before_request(before_request)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_views.route('/', methods=['GET', 'POST'])
|
||||||
|
def index():
|
||||||
|
return render_template('admin/index.html.j2')
|
||||||
|
|
||||||
|
|
||||||
|
@admin_views.route('/user', methods=['GET'])
|
||||||
|
def users():
|
||||||
|
users = User.query.all()
|
||||||
|
return render_template('admin/users.html.j2', users=users)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_views.route('/registrations', methods=['GET'])
|
||||||
|
def registrations():
|
||||||
|
users = UserSignUp.query.all()
|
||||||
|
return render_template('admin/registrations.html.j2', users=users)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_views.route('/registration/<registration_id>', methods=['DELETE'])
|
||||||
|
def registration_delete(registration_id):
|
||||||
|
user_data = UserSignUp.query.get(registration_id)
|
||||||
|
if user_data is None:
|
||||||
|
return jsonify({}), 404
|
||||||
|
db.session.delete(user_data)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({})
|
||||||
|
|
||||||
|
|
||||||
|
@admin_views.route('/registration/<registration_id>', methods=['PUT'])
|
||||||
|
def registration_accept(registration_id):
|
||||||
|
user_data = UserSignUp.query.get(registration_id)
|
||||||
|
#create user
|
||||||
|
user = User.new(user_data)
|
||||||
|
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.delete(user_data)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({})
|
|
@ -7,13 +7,15 @@ from flask import current_app, session
|
||||||
from flask.templating import render_template
|
from flask.templating import render_template
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
|
|
||||||
from flask import request, url_for
|
from flask import request, url_for, jsonify
|
||||||
from flask_login import login_required, login_user, logout_user, current_user
|
from flask_login import login_required, login_user, logout_user, current_user
|
||||||
import logging
|
import logging
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
import http
|
import http
|
||||||
import crypt
|
import crypt
|
||||||
|
import ory_hydra_client
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from ..model import db, User, SecurityUser, UserSignUp
|
from ..model import db, User, SecurityUser, UserSignUp
|
||||||
from ..form.auth import ConsentForm, LoginForm, RegistrationForm
|
from ..form.auth import ConsentForm, LoginForm, RegistrationForm
|
||||||
|
@ -31,8 +33,11 @@ def consent():
|
||||||
form = ConsentForm()
|
form = ConsentForm()
|
||||||
remember_for = 60*60*24*30 # remember for 7 days
|
remember_for = 60*60*24*30 # remember for 7 days
|
||||||
|
|
||||||
consent_request = current_app.hydra_api.get_consent_request(
|
try:
|
||||||
request.args['consent_challenge'])
|
consent_request = current_app.hydra_api.get_consent_request(
|
||||||
|
request.args['consent_challenge'])
|
||||||
|
except ory_hydra_client.exceptions.ApiException:
|
||||||
|
return redirect(url_for('frontend.index'))
|
||||||
|
|
||||||
requested_scope = consent_request.requested_scope
|
requested_scope = consent_request.requested_scope
|
||||||
requested_audiences = consent_request.requested_access_token_audience
|
requested_audiences = consent_request.requested_access_token_audience
|
||||||
|
@ -40,9 +45,11 @@ def consent():
|
||||||
if form.validate_on_submit() or consent_request.skip:
|
if form.validate_on_submit() or consent_request.skip:
|
||||||
user = User.query.get(consent_request.subject)
|
user = User.query.get(consent_request.subject)
|
||||||
token_data = {
|
token_data = {
|
||||||
|
'name': str(user.username),
|
||||||
'preferred_username': str(user.username),
|
'preferred_username': str(user.username),
|
||||||
'email': str(user.email),
|
'email': str(user.email),
|
||||||
'email_verified': True,
|
'email_verified': True,
|
||||||
|
'groups': [group.name for group in user.groups]
|
||||||
}
|
}
|
||||||
id_token_data = {}
|
id_token_data = {}
|
||||||
if 'openid' in requested_scope:
|
if 'openid' in requested_scope:
|
||||||
|
@ -70,7 +77,10 @@ def consent():
|
||||||
@auth_views.route('/login', methods=['GET', 'POST'])
|
@auth_views.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
login_challenge = request.args.get('login_challenge')
|
login_challenge = request.args.get('login_challenge')
|
||||||
login_request = current_app.hydra_api.get_login_request(login_challenge)
|
try:
|
||||||
|
login_request = current_app.hydra_api.get_login_request(login_challenge)
|
||||||
|
except ory_hydra_client.exceptions.ApiValueError:
|
||||||
|
return redirect(url_for('frontend.index'))
|
||||||
|
|
||||||
if login_request.skip:
|
if login_request.skip:
|
||||||
resp = current_app.hydra_api.accept_login_request(
|
resp = current_app.hydra_api.accept_login_request(
|
||||||
|
@ -93,7 +103,11 @@ def login():
|
||||||
@auth_views.route('/login/auth', methods=['GET', 'POST'])
|
@auth_views.route('/login/auth', methods=['GET', 'POST'])
|
||||||
def login_auth():
|
def login_auth():
|
||||||
login_challenge = request.args.get('login_challenge')
|
login_challenge = request.args.get('login_challenge')
|
||||||
login_request = current_app.hydra_api.get_login_request(login_challenge)
|
try:
|
||||||
|
login_request = current_app.hydra_api.get_login_request(login_challenge)
|
||||||
|
except ory_hydra_client.exceptions.ApiValueError:
|
||||||
|
return redirect(url_for('frontend.index'))
|
||||||
|
|
||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
auth_forms = {}
|
auth_forms = {}
|
||||||
|
@ -115,7 +129,8 @@ def login_auth():
|
||||||
# db.session.commit()
|
# db.session.commit()
|
||||||
|
|
||||||
subject = user.id
|
subject = user.id
|
||||||
|
user.last_login = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
resp = current_app.hydra_api.accept_login_request(
|
resp = current_app.hydra_api.accept_login_request(
|
||||||
login_challenge, body={
|
login_challenge, body={
|
||||||
'subject': subject,
|
'subject': subject,
|
||||||
|
@ -135,8 +150,13 @@ def logout():
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@auth_views.route("/sign_up", methods=["GET", "POST"])
|
@auth_views.route("/sign_up", methods=["GET"])
|
||||||
def sign_up():
|
def sign_up():
|
||||||
|
form = RegistrationForm()
|
||||||
|
return render_template('auth/sign_up.html.j2', form=form)
|
||||||
|
|
||||||
|
@auth_views.route("/sign_up", methods=["POST"])
|
||||||
|
def sign_up_submit():
|
||||||
form = RegistrationForm()
|
form = RegistrationForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = UserSignUp()
|
user = UserSignUp()
|
||||||
|
@ -145,5 +165,8 @@ def sign_up():
|
||||||
user.alternative_email = form.data['alternative_email']
|
user.alternative_email = form.data['alternative_email']
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
return jsonify({})
|
||||||
return render_template('auth/sign_up.html.j2', form=form)
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'errors': form.errors
|
||||||
|
})
|
||||||
|
|
|
@ -27,8 +27,10 @@ def before_request():
|
||||||
try:
|
try:
|
||||||
resp = current_app.oauth.session.get('/userinfo')
|
resp = current_app.oauth.session.get('/userinfo')
|
||||||
if not current_user.is_authenticated or resp.status_code is not 200:
|
if not current_user.is_authenticated or resp.status_code is not 200:
|
||||||
|
logout_user()
|
||||||
return redirect(url_for('oauth.login'))
|
return redirect(url_for('oauth.login'))
|
||||||
except TokenExpiredError:
|
except TokenExpiredError:
|
||||||
|
logout_user()
|
||||||
return redirect(url_for('oauth.login'))
|
return redirect(url_for('oauth.login'))
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
37
templates/admin/base.html.j2
Normal file
37
templates/admin/base.html.j2
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<nav class="col-md-2 d-none d-md-block bg-light sidebar fixed-top">
|
||||||
|
<div class="sidebar-sticky active">
|
||||||
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.users') }}">{{ gettext('users') }}</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin.registrations') }}">{{ gettext('registrations') }}</a></li>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="col-md-9 ml-sm-auto col-lg-10 px-4" role="main">
|
||||||
|
<h1>{% block title %}{% endblock %}</h1>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body mt-5 mb-5">
|
||||||
|
<div class="tab-content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
2
templates/admin/index.html.j2
Normal file
2
templates/admin/index.html.j2
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
{% extends 'admin/base.html.j2' %}
|
28
templates/admin/registrations.html.j2
Normal file
28
templates/admin/registrations.html.j2
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends 'admin/base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}{{ gettext('registrations') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>username</th>
|
||||||
|
<th>created_at</th>
|
||||||
|
<th>action<th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in users %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.username }}</td>
|
||||||
|
<td>{{ user.created_at }}</td>
|
||||||
|
<td>
|
||||||
|
<a title="{{ gettext('Reject')}}" href="{{ url_for('.registration_delete', registration_id=user.id) }}" onclick="admin.registration.delete(this.href, '{{ user.username }}'); return false;"><i class="fas fa-ban"></i></a>
|
||||||
|
<a title="{{ gettext('Reject')}}" href="{{ url_for('.registration_accept', registration_id=user.id) }}" onclick="admin.registration.accept(this.href, '{{ user.username }}'); return false;"><i class="fas fa-check"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock%}
|
26
templates/admin/users.html.j2
Normal file
26
templates/admin/users.html.j2
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends 'admin/base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}{{ gettext('users') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>username</th>
|
||||||
|
<th>created_at</th>
|
||||||
|
<th>modified_at<th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in users %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.username }}</td>
|
||||||
|
<td>{{ user.created_at }}</td>
|
||||||
|
<td>{{ user.modified_at }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock%}
|
|
@ -2,12 +2,7 @@
|
||||||
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
|
|
||||||
<div class="col-xs-1"><button id="sidebarCollapse" class="btn btn-primary d-xs-block d-md-none" ><i class="fa fa-bars fa-2x"></i></button></div>
|
|
||||||
<div class="col-xs-11"><a class="navbar-brand col-xs-11 col-sm-3 col-md-2 mr-0" href="/">Lenticular Cloud</a></div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div style="height: 40px"></div>
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% for message in get_flashed_messages() %}
|
{% for message in get_flashed_messages() %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{{ render_form(form) }}
|
{{ render_form(form, onsubmit="return auth.sign_up.submit(this)", action_url=url_for('auth.sign_up_submit')) }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -153,9 +153,9 @@
|
||||||
action_text - text of submit button
|
action_text - text of submit button
|
||||||
class_ - sets a class for form
|
class_ - sets a class for form
|
||||||
#}
|
#}
|
||||||
{% macro render_form(form, action_url='', class_='', method='post') -%}
|
{% macro render_form(form, action_url='', class_='', method='post', onsubmit='') -%}
|
||||||
|
|
||||||
<form method="{{ method }}" {% if action_url %}action="{{ action_url }}" {% endif %}role="form" class="{{ class_ }}">
|
<form method="{{ method }}" {% if action_url %}action="{{ action_url }}" {% endif %}role="form" class="{{ class_ }}" {% if onsubmit %}onsubmit="{{ onsubmit }}"{% endif %}>
|
||||||
<input name="form" type="hidden" value="{{ form.__class__.__name__ }}">
|
<input name="form" type="hidden" value="{{ form.__class__.__name__ }}">
|
||||||
{{ _render_form(form) }}
|
{{ _render_form(form) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -3,13 +3,6 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
|
|
||||||
<div class="col-xs-1"><button id="sidebarCollapse" class="btn btn-primary d-xs-block d-md-none" ><i class="fa fa-bars fa-2x"></i></button></div>
|
|
||||||
<div class="col-xs-11"><a class="navbar-brand col-xs-11 col-sm-3 col-md-2 mr-0" href="/">Lenticular Cloud</a></div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
|
||||||
<div style="height: 40px"></div>
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% for message in get_flashed_messages() %}
|
{% for message in get_flashed_messages() %}
|
||||||
|
|
|
@ -7,20 +7,25 @@
|
||||||
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<th>{{ gettext('Client ID') }}</th>
|
<tr>
|
||||||
<th>{{ gettext('Remember me') }}</th>
|
<th>{{ gettext('Client ID') }}</th>
|
||||||
<th>{{ gettext('Created at') }}
|
<th>{{ gettext('Remember') }}</th>
|
||||||
<th>{{ gettext('Action') }}
|
<th>{{ gettext('Created at') }}
|
||||||
|
<th>{{ gettext('Action') }}
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for consent_session in consent_sessions %}
|
<tbody>
|
||||||
<tr>
|
{% for consent_session in consent_sessions %}
|
||||||
<td>{{ consent_session.consent_request.client.client_id }}</td>
|
<tr>
|
||||||
<td>{{ consent_session.remember }}</td>
|
<td>{{ consent_session.consent_request.client.client_id }}</td>
|
||||||
<td>{{ consent_session.handled_at }}</td>
|
<td>{{ consent_session.remember }}</td>
|
||||||
<td>
|
<td>{{ consent_session.handled_at }}</td>
|
||||||
<a title="{{ gettext('Revoke')}}" href="{{ url_for('.oauth2_token_revoke', client_id=consent_session.consent_request.client.client_id) }}" onclick="oauth2_token.revoke(this.href, '{{ consent_session.consent_request.client.client_id }}'); return false;"><i class="fas fa-ban"></i></a>
|
<td>
|
||||||
</td>
|
<a title="{{ gettext('Revoke')}}" href="{{ url_for('.oauth2_token_revoke', client_id=consent_session.consent_request.client.client_id) }}" onclick="oauth2_token.revoke(this.href, '{{ consent_session.consent_request.client.client_id }}'); return false;"><i class="fas fa-ban"></i></a>
|
||||||
</tr>
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -10,26 +10,27 @@
|
||||||
<body>
|
<body>
|
||||||
<div class='messages-box'>
|
<div class='messages-box'>
|
||||||
</div>
|
</div>
|
||||||
<template id='confirm-dialog-template'>
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
|
|
||||||
<div class="modal-header">
|
<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
|
||||||
<h5 class="modal-title"></h5>
|
{% if current_user.is_authenticated %}
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
<a class="nav-link" href="{{ url_for('frontend.logout') }}">{{ gettext('Logout') }}</a>
|
||||||
</div>
|
Hallo {{ current_user.username }}
|
||||||
|
{% endif %}
|
||||||
|
<div class="col-xs-1"><button id="sidebarCollapse" class="btn btn-primary d-xs-block d-md-none" ><i class="fa fa-bars fa-2x"></i></button></div>
|
||||||
|
<div class="col-xs-11"><a class="navbar-brand col-xs-11 col-sm-3 col-md-2 mr-0" href="/">Lenticular Cloud</a></div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div class="modal-body">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div style="height: 40px"></div>
|
||||||
<button type="button" class="btn btn-danger close" data-dismiss="modal">Cancel</button>
|
|
||||||
<button type="button" class="btn btn-primary btn-ok process">Process</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
|
<div class='container'>
|
||||||
|
<div class="mt-5 row justify-content-center">
|
||||||
|
<footer>
|
||||||
|
<span class="text-muted">Render Time: {{ g.request_time() }}</span> | <span class="text-muted">{{ gettext('All right reserved. ©') + '2020' }}</span>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="application/javascript" src="/static/main.js?v={{ GIT_HASH }}" ></script>
|
<script type="application/javascript" src="/static/main.js?v={{ GIT_HASH }}" ></script>
|
||||||
<script type="application/javascript" >
|
<script type="application/javascript" >
|
||||||
{% block script_js %}{% endblock %}
|
{% block script_js %}{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue