merge ldap user and db user, cleanup, add password change
This commit is contained in:
parent
6334c993a9
commit
4150853588
|
@ -3,10 +3,29 @@
|
||||||
const $ = document.querySelector.bind(document)
|
const $ = document.querySelector.bind(document)
|
||||||
const _ = document.getElementById
|
const _ = document.getElementById
|
||||||
|
|
||||||
export class ConfirmDialog {
|
export class Dialog {
|
||||||
|
template(){
|
||||||
|
return `
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
constructor(message) {
|
<div class="modal-header">
|
||||||
this._div = document.getElementById('confirm-dialog-template').content.querySelector('div').cloneNode(true);
|
<h5 class="modal-title"></h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary close" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(title, message) {
|
||||||
|
this._div = (new DOMParser().parseFromString(this.template(), 'text/html')).body.firstChild;
|
||||||
this._div.querySelector('.modal-body').innerHTML = message;
|
this._div.querySelector('.modal-body').innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,21 +37,16 @@ export class ConfirmDialog {
|
||||||
});
|
});
|
||||||
|
|
||||||
this._div.querySelectorAll('.close').forEach(function (o){
|
this._div.querySelectorAll('.close').forEach(function (o){
|
||||||
o.onclick=self.cancel.bind(self);
|
o.onclick=self.close.bind(self);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._div.querySelector('.process').onclick = () => {
|
|
||||||
self._close();
|
|
||||||
self._resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.messages-box').appendChild(this._div);
|
$('.messages-box').appendChild(this._div);
|
||||||
return this._promise
|
return this._promise
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
close() {
|
||||||
this._close()
|
this._close()
|
||||||
this._reject('canceled by user');
|
this._resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
_close() {
|
_close() {
|
||||||
|
@ -43,3 +57,38 @@ export class ConfirmDialog {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class ConfirmDialog extends Dialog {
|
||||||
|
template(){
|
||||||
|
return `
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"></h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<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>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(){
|
||||||
|
this._div.querySelector('.process').onclick = () => {
|
||||||
|
self._close();
|
||||||
|
self._resolve();
|
||||||
|
};
|
||||||
|
return super.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._close()
|
||||||
|
this._reject('canceled by user');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'jquery';
|
import 'jquery';
|
||||||
import 'bootstrap';
|
import 'bootstrap';
|
||||||
import 'jquery-form'
|
import 'jquery-form'
|
||||||
import {ConfirmDialog} from './confirm-modal.js';
|
import {ConfirmDialog, Dialog} from './confirm-modal.js';
|
||||||
|
|
||||||
jQuery = window.$ = window.jQuery = require('jquery');
|
jQuery = window.$ = window.jQuery = require('jquery');
|
||||||
var forge = require('node-forge');
|
var forge = require('node-forge');
|
||||||
|
@ -39,6 +39,7 @@ function randBase32() {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.ConfirmDialog = ConfirmDialog;
|
window.ConfirmDialog = ConfirmDialog;
|
||||||
|
window.Dialog = Dialog;
|
||||||
|
|
||||||
window.$(document).ready(function () {
|
window.$(document).ready(function () {
|
||||||
$('#sidebarCollapse').onclick = function () {
|
$('#sidebarCollapse').onclick = function () {
|
||||||
|
@ -58,7 +59,7 @@ window.totp = {
|
||||||
input_secret.value = secret;
|
input_secret.value = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.querySelector('#name').on('change',window.totp.generate_qrcode);
|
form.querySelector('#name').onchange=window.totp.generate_qrcode;
|
||||||
window.totp.generate_qrcode();
|
window.totp.generate_qrcode();
|
||||||
},
|
},
|
||||||
generate_qrcode: function(){
|
generate_qrcode: function(){
|
||||||
|
@ -68,7 +69,13 @@ window.totp = {
|
||||||
var issuer = 'Lenticular%20Cloud';
|
var issuer = 'Lenticular%20Cloud';
|
||||||
var svg_container = $('#svg-container')
|
var svg_container = $('#svg-container')
|
||||||
var svg = new QRCode(`otpauth://totp/${issuer}:${name}?secret=${secret}&issuer=${issuer}`).svg();
|
var svg = new QRCode(`otpauth://totp/${issuer}:${name}?secret=${secret}&issuer=${issuer}`).svg();
|
||||||
svg_container.html(svg);
|
var svg_xml =new DOMParser().parseFromString(svg,'text/xml')
|
||||||
|
if(svg_container.childNodes.length > 0) {
|
||||||
|
svg_container.childNodes[0].replaceWith(svg_xml.childNodes[0])
|
||||||
|
} else {
|
||||||
|
svg_container.appendChild(svg_xml.childNodes[0]);
|
||||||
|
}
|
||||||
|
// .innerHtml=svg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,15 +87,30 @@ window.fido2 = {
|
||||||
window.password_change= {
|
window.password_change= {
|
||||||
init: function(){
|
init: function(){
|
||||||
var form = $('form');
|
var form = $('form');
|
||||||
|
form.onsubmit = function () {
|
||||||
SimpleFormSubmit.submitForm(form.action, form)
|
SimpleFormSubmit.submitForm(form.action, form)
|
||||||
.then(response =>{
|
.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('Password change Error', `Error Happend: ${msg}`).show()
|
||||||
|
} else {
|
||||||
|
new Dialog('Password changed', 'Password changed successfully!').show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.oauth2_token = {
|
window.oauth2_token = {
|
||||||
revoke: function(href, id){
|
revoke: function(href, id){
|
||||||
var dialog = new ConfirmDialog(`Are you sure to revoke all tokens from client "${id}"?`);
|
var dialog = new ConfirmDialog('Revoke client tokens', `Are you sure to revoke all tokens from client "${id}"?`);
|
||||||
dialog.show().then(()=>{
|
dialog.show().then(()=>{
|
||||||
fetch(href, {
|
fetch(href, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
|
@ -151,7 +173,7 @@ window.client_cert = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
revoke_certificate: function(href, id){
|
revoke_certificate: function(href, id){
|
||||||
var dialog = new ConfirmDialog(`Are you sure to revoke the certificate with the fingerprint ${id}?`);
|
var dialog = new ConfirmDialog('Revoke client certificate', `Are you sure to revoke the certificate with the fingerprint ${id}?`);
|
||||||
dialog.show().then(()=>{
|
dialog.show().then(()=>{
|
||||||
fetch(href, {
|
fetch(href, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
|
|
|
@ -40,7 +40,7 @@ def init_app(name=None):
|
||||||
model.ldap_conn = app.ldap_conn
|
model.ldap_conn = app.ldap_conn
|
||||||
model.base_dn = app.config['LDAP_BASE_DN']
|
model.base_dn = app.config['LDAP_BASE_DN']
|
||||||
|
|
||||||
from .model_db import db
|
from .model import db
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from .form.auth import PasswordForm, TotpForm, Fido2Form
|
from .form.auth import PasswordForm, TotpForm, Fido2Form
|
||||||
from ldap3 import Server, Connection
|
from ldap3 import Server, Connection, HASHED_SALTED_SHA256
|
||||||
from ldap3.core.exceptions import LDAPException
|
from ldap3.core.exceptions import LDAPException
|
||||||
|
from .model import User
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
import pyotp
|
|
||||||
|
|
||||||
class AuthProvider:
|
class AuthProvider:
|
||||||
|
|
||||||
|
@ -31,8 +36,9 @@ class LdapAuthProvider(AuthProvider):
|
||||||
return PasswordForm(prefix='password')
|
return PasswordForm(prefix='password')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_auth(user, form):
|
def check_auth(user: User, form):
|
||||||
return LdapAuthProvider.check_auth_internal(user, form.data['password'])
|
return LdapAuthProvider.check_auth_internal(
|
||||||
|
user, form.data['password'])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_auth_internal(user, password):
|
def check_auth_internal(user, password):
|
||||||
|
|
|
@ -35,9 +35,9 @@ class TOTPDeleteForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeForm(FlaskForm):
|
class PasswordChangeForm(FlaskForm):
|
||||||
old_password = PasswordField(gettext('Old Password'), validators=[DataRequired()])
|
password_old = PasswordField(gettext('Old Password'), validators=[DataRequired()])
|
||||||
password = PasswordField(gettext('New Password'), validators=[DataRequired()])
|
password_new = PasswordField(gettext('New Password'), validators=[DataRequired()])
|
||||||
password_repeat = PasswordField(gettext('Repeat Password'), validators=[DataRequired(),EqualTo('password')])
|
password_repeat = PasswordField(gettext('Repeat Password'), validators=[DataRequired(),EqualTo('password_new')])
|
||||||
submit = SubmitField(gettext('Change Password'))
|
submit = SubmitField(gettext('Change Password'))
|
||||||
|
|
||||||
class OidcAuthenticationConfirm(FlaskForm):
|
class OidcAuthenticationConfirm(FlaskForm):
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from ldap3_orm import AttrDef, EntryBase as _EntryBase, ObjectDef, EntryType
|
from ldap3_orm import AttrDef, EntryBase as _EntryBase, ObjectDef, EntryType
|
||||||
from ldap3_orm import Reader
|
from ldap3_orm import Reader
|
||||||
from ldap3 import Entry
|
from ldap3 import Entry, HASHED_SALTED_SHA256
|
||||||
from ldap3.utils.conv import escape_filter_chars
|
from ldap3.utils.conv import escape_filter_chars
|
||||||
|
from ldap3.utils.hashed import hashed
|
||||||
from flask_login import UserMixin
|
from flask_login import UserMixin
|
||||||
from ldap3.core.exceptions import LDAPSessionTerminatedByServerError
|
from ldap3.core.exceptions import LDAPSessionTerminatedByServerError
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
@ -12,10 +13,21 @@ from datetime import datetime
|
||||||
from dateutil import tz
|
from dateutil import tz
|
||||||
import pyotp
|
import pyotp
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import crypt
|
||||||
|
from flask_sqlalchemy import SQLAlchemy, orm
|
||||||
|
from datetime import datetime
|
||||||
|
import uuid
|
||||||
|
import pyotp
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
ldap_conn = None # type: Connection
|
ldap_conn = None # type: Connection
|
||||||
base_dn = ''
|
base_dn = ''
|
||||||
|
|
||||||
|
db = SQLAlchemy() # type: SQLAlchemy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityUser(UserMixin):
|
class SecurityUser(UserMixin):
|
||||||
|
|
||||||
|
@ -35,16 +47,18 @@ class LambdaStr:
|
||||||
return self.lam()
|
return self.lam()
|
||||||
|
|
||||||
|
|
||||||
class EntryBase(object):
|
class EntryBase(db.Model):
|
||||||
|
__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
|
_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):
|
||||||
if ldap_object is None:
|
# if ldap_object is None:
|
||||||
self._ldap_object = self.get_type()(**kwargs)
|
# self._ldap_object = self.get_type()(**kwargs)
|
||||||
else:
|
# else:
|
||||||
self._ldap_object = ldap_object
|
# self._ldap_object = ldap_object
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self._ldap_object)
|
return str(self._ldap_object)
|
||||||
|
@ -64,14 +78,17 @@ class EntryBase(object):
|
||||||
return cls._type
|
return cls._type
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
|
self._ldap_object.entry_commit_changes()
|
||||||
|
|
||||||
|
def add(self):
|
||||||
print(self._ldap_object.entry_attributes_as_dict)
|
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.dn, self.object_classes, self._ldap_object.entry_attributes_as_dict)
|
||||||
print(ret)
|
logger.debug(ret)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query(cls):
|
def query_(cls):
|
||||||
if cls._query_object is None:
|
if cls._query_object is None:
|
||||||
cls._query_object = cls._query(cls)
|
cls._query_object = cls._query(cls)
|
||||||
return cls._query_object
|
return cls._query_object
|
||||||
|
@ -188,77 +205,26 @@ class Certificate(object):
|
||||||
return f'Certificate(cn={self._cn}, ca_name={self._ca_name}, not_valid_before={self.not_valid_before}, not_valid_after={self.not_valid_after})'
|
return f'Certificate(cn={self._cn}, ca_name={self._ca_name}, not_valid_before={self.not_valid_before}, not_valid_after={self.not_valid_after})'
|
||||||
|
|
||||||
|
|
||||||
class Totp(object):
|
def generate_uuid():
|
||||||
|
return str(uuid.uuid4())
|
||||||
def __init__(self, name, secret, created_at=datetime.now()):
|
|
||||||
self._secret = secret
|
|
||||||
self._name = name
|
|
||||||
self._created_at = created_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def created_at(self):
|
|
||||||
return self._created_at
|
|
||||||
|
|
||||||
def verify(self, token: str):
|
|
||||||
totp = pyotp.TOTP(self._secret)
|
|
||||||
return totp.verify(token)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
'secret': self._secret,
|
|
||||||
'name': self._name,
|
|
||||||
'created_at': int(self._created_at.timestamp())}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_dict(data):
|
|
||||||
return Totp(
|
|
||||||
name=data['name'],
|
|
||||||
secret=data['secret'],
|
|
||||||
created_at=datetime.fromtimestamp(data['created_at']))
|
|
||||||
|
|
||||||
|
|
||||||
class TotpList(MutableSequence):
|
|
||||||
def __init__(self, ldap_attr):
|
|
||||||
super().__init__()
|
|
||||||
self._ldap_attr = ldap_attr
|
|
||||||
|
|
||||||
def __getitem__(self, ii):
|
|
||||||
return Totp.from_dict(json.loads(self._ldap_attr[ii]))
|
|
||||||
|
|
||||||
def __setitem__(self, ii, val: Totp):
|
|
||||||
self._ldap_attr[ii] = json.dumps(val.to_dict()).encode()
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._ldap_attr)
|
|
||||||
|
|
||||||
def __delitem__(self, ii):
|
|
||||||
del self._ldap_attr[ii]
|
|
||||||
|
|
||||||
def delete(self, totp_name):
|
|
||||||
for i in range(len(self)):
|
|
||||||
if self[i].name == totp_name:
|
|
||||||
self._ldap_attr.delete(self._ldap_attr[i])
|
|
||||||
|
|
||||||
def insert(self, ii, val):
|
|
||||||
self.append(val)
|
|
||||||
|
|
||||||
def append(self, val):
|
|
||||||
self._ldap_attr.add(json.dumps(val.to_dict()).encode())
|
|
||||||
|
|
||||||
|
|
||||||
class User(EntryBase):
|
class User(EntryBase):
|
||||||
|
id = db.Column(
|
||||||
|
db.String(length=36), primary_key=True, default=generate_uuid)
|
||||||
|
username = db.Column(
|
||||||
|
db.String, unique=True)
|
||||||
|
|
||||||
|
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, ldap_object=None, **kwargs):
|
def __init__(self,**kwargs):
|
||||||
super().__init__(ldap_object, **kwargs)
|
self._ldap_object = None
|
||||||
self._totp_list = TotpList(ldap_object.totpSecret)
|
super(db.Model).__init__(**kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
|
@ -269,24 +235,11 @@ 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()
|
||||||
self._totp_list = TotpList(self._ldap_object.totpSecret)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_dn(self):
|
def entry_dn(self):
|
||||||
return self._ldap_object.entry_dn
|
return self._ldap_object.entry_dn
|
||||||
|
|
||||||
@property
|
|
||||||
def username(self):
|
|
||||||
return self._ldap_object.uid
|
|
||||||
|
|
||||||
@username.setter
|
|
||||||
def username(self, value):
|
|
||||||
self._ldap_object.uid = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def userPassword(self):
|
|
||||||
return self._ldap_object.userPassword
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fullname(self):
|
def fullname(self):
|
||||||
return self._ldap_object.fullname
|
return self._ldap_object.fullname
|
||||||
|
@ -315,14 +268,24 @@ class User(EntryBase):
|
||||||
def gpg_public_key(self):
|
def gpg_public_key(self):
|
||||||
return self._ldap_object.gpgPublicKey
|
return self._ldap_object.gpgPublicKey
|
||||||
|
|
||||||
@property
|
def change_password(self, password_new: str):
|
||||||
def totps(self):
|
self.make_writeable()
|
||||||
return self._totp_list
|
password_hashed = crypt.crypt(password_new)
|
||||||
|
self._ldap_object.userPassword = ('{CRYPT}' + password_hashed).encode()
|
||||||
|
self.commit()
|
||||||
|
|
||||||
class _query(EntryBase._query):
|
class _query(EntryBase._query):
|
||||||
|
|
||||||
def _mapping(self, ldap_object):
|
def _mapping(self, ldap_object):
|
||||||
return User(ldap_object=ldap_object)
|
user = User.query.filter(User.username == str(ldap_object.uid)).first()
|
||||||
|
if user is None:
|
||||||
|
# migration time
|
||||||
|
user = User()
|
||||||
|
user.username = str(ldap_object.uid)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
user._ldap_object = ldap_object
|
||||||
|
return user
|
||||||
|
|
||||||
def by_username(self, username) -> 'User':
|
def by_username(self, username) -> 'User':
|
||||||
result = self._query('(uid={username:s})'.format(username=escape_filter_chars(username)))
|
result = self._query('(uid={username:s})'.format(username=escape_filter_chars(username)))
|
||||||
|
@ -333,7 +296,23 @@ class User(EntryBase):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Totp(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
secret = db.Column(db.String, nullable=False)
|
||||||
|
name = db.Column(db.String, nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
|
||||||
|
|
||||||
|
user_id = db.Column(
|
||||||
|
db.String(length=36),
|
||||||
|
db.ForeignKey(User.id), nullable=False)
|
||||||
|
user = db.relationship(User)
|
||||||
|
|
||||||
|
def verify(self, token: str):
|
||||||
|
totp = pyotp.TOTP(self.secret)
|
||||||
|
return totp.verify(token)
|
||||||
|
|
||||||
class Group(EntryBase):
|
class Group(EntryBase):
|
||||||
|
__abstract__ = True # for sqlalchemy, disable for now
|
||||||
dn = "cn={cn},{base_dn}"
|
dn = "cn={cn},{base_dn}"
|
||||||
base_dn = "ou=Users,{_base_dn}"
|
base_dn = "ou=Users,{_base_dn}"
|
||||||
object_classes = ["top"]
|
object_classes = ["top"]
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from flask_sqlalchemy import SQLAlchemy, orm
|
from flask_sqlalchemy import SQLAlchemy, orm
|
||||||
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
import pyotp
|
||||||
|
|
||||||
db = SQLAlchemy() # type: SQLAlchemy
|
db = SQLAlchemy() # type: SQLAlchemy
|
||||||
|
|
||||||
|
@ -8,39 +10,26 @@ def generate_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
class OAuth(db.Model):
|
|
||||||
token = db.Column(db.Text, primary_key=True)
|
|
||||||
provider = db.Column(db.Text)
|
|
||||||
provider_username = db.Column(db.Text)
|
|
||||||
|
|
||||||
|
|
||||||
class User(db.Model):
|
class User(db.Model):
|
||||||
id = db.Column(db.String(length=36), primary_key=True, default=generate_uuid)
|
id = db.Column(
|
||||||
username = db.Column(db.String, unique=True)
|
db.String(length=36), primary_key=True, default=generate_uuid)
|
||||||
|
username = db.Column(
|
||||||
|
db.String, unique=True)
|
||||||
|
|
||||||
|
totps = db.relationship('Totp', back_populates='user')
|
||||||
|
|
||||||
|
|
||||||
class Client(db.Model):
|
class Totp(object):
|
||||||
key = db.Column(db.Text, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
value = db.Column(db.Text)
|
secret = db.Column(db.String, nullable=False)
|
||||||
|
name = db.Column(db.String, nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
|
||||||
class AuthzCode(db.Model):
|
|
||||||
key = db.Column(db.Text, primary_key=True)
|
|
||||||
value = db.Column(db.Text)
|
|
||||||
|
|
||||||
|
|
||||||
class AccessToken(db.Model):
|
|
||||||
key = db.Column(db.Text, primary_key=True)
|
|
||||||
value = db.Column(db.Text)
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshToken(db.Model):
|
|
||||||
key = db.Column(db.Text, primary_key=True)
|
|
||||||
value = db.Column(db.Text)
|
|
||||||
|
|
||||||
|
|
||||||
class SubjectIdentifier(db.Model):
|
|
||||||
key = db.Column(db.Text, primary_key=True)
|
|
||||||
value = db.Column(db.Text)
|
|
||||||
|
|
||||||
|
user_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey(User.id), nullable=False)
|
||||||
|
user = db.relationship(User)
|
||||||
|
|
||||||
|
def verify(self, token: str):
|
||||||
|
totp = pyotp.TOTP(self._secret)
|
||||||
|
return totp.verify(token)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from ..model import User
|
from ..model import User
|
||||||
from ..model_db import User as DbUser
|
|
||||||
from ..auth_providers import LdapAuthProvider
|
from ..auth_providers import LdapAuthProvider
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,11 +18,15 @@ api_views = Blueprint('api', __name__, url_prefix='/api')
|
||||||
|
|
||||||
@api_views.route('/userinfo', methods=['GET', 'POST'])
|
@api_views.route('/userinfo', methods=['GET', 'POST'])
|
||||||
def userinfo():
|
def userinfo():
|
||||||
|
if 'authorization' not in request.headers:
|
||||||
|
return 'not token found', 400
|
||||||
token = request.headers['authorization'].replace('Bearer ', '')
|
token = request.headers['authorization'].replace('Bearer ', '')
|
||||||
token_info = current_app.hydra_api.introspect_o_auth2_token(token=token)
|
token_info = current_app.hydra_api.introspect_o_auth2_token(token=token)
|
||||||
|
if not token_info.active:
|
||||||
|
return 'token not valid', 403
|
||||||
|
|
||||||
user_db = DbUser.query.get(token_info.sub)
|
user_db = User.query.get(token_info.sub)
|
||||||
user = User.query().by_username(user_db.username)
|
user = User.query_().by_username(user_db.username)
|
||||||
|
|
||||||
public_url = current_app.config.get('HYDRA_PUBLIC_URL')
|
public_url = current_app.config.get('HYDRA_PUBLIC_URL')
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
|
@ -50,5 +53,6 @@ def user_list():
|
||||||
if 'lc_i_userlist' not in token_info.scope.split(' '):
|
if 'lc_i_userlist' not in token_info.scope.split(' '):
|
||||||
return '', 403
|
return '', 403
|
||||||
|
|
||||||
return jsonify([{'username': str(user.username), 'email': str(user.email)}
|
return jsonify([
|
||||||
for user in User.query().all()])
|
{'username': str(user.username), 'email': str(user.email)}
|
||||||
|
for user in User.query_().all()])
|
||||||
|
|
|
@ -14,8 +14,7 @@ from urllib.parse import urlparse
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
import http
|
import http
|
||||||
|
|
||||||
from ..model import User, SecurityUser
|
from ..model import db, User, SecurityUser
|
||||||
from ..model_db import db, User as DbUser
|
|
||||||
from ..form.auth import ConsentForm, LoginForm
|
from ..form.auth import ConsentForm, LoginForm
|
||||||
from ..auth_providers import AUTH_PROVIDER_LIST
|
from ..auth_providers import AUTH_PROVIDER_LIST
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ def consent():
|
||||||
# DUMMPY ONLY
|
# DUMMPY ONLY
|
||||||
|
|
||||||
form = ConsentForm()
|
form = ConsentForm()
|
||||||
remember_for = 60*60*24*7 # remember for 7 days
|
remember_for = 60*60*24*30 # remember for 7 days
|
||||||
|
|
||||||
consent_request = current_app.hydra_api.get_consent_request(
|
consent_request = current_app.hydra_api.get_consent_request(
|
||||||
request.args['consent_challenge'])
|
request.args['consent_challenge'])
|
||||||
|
@ -42,7 +41,7 @@ def consent():
|
||||||
'grant_scope': requested_scope,
|
'grant_scope': requested_scope,
|
||||||
'grant_access_token_audience': requested_audiences,
|
'grant_access_token_audience': requested_audiences,
|
||||||
'remember': form.data['remember'],
|
'remember': form.data['remember'],
|
||||||
# 'remember_for': remember_for,
|
'remember_for': remember_for,
|
||||||
})
|
})
|
||||||
return redirect(resp.redirect_to)
|
return redirect(resp.redirect_to)
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -65,7 +64,7 @@ def login():
|
||||||
return redirect(resp.redirect_to)
|
return redirect(resp.redirect_to)
|
||||||
form = LoginForm()
|
form = LoginForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User.query().by_username(form.data['name'])
|
user = User.query_().by_username(form.data['name'])
|
||||||
if user:
|
if user:
|
||||||
session['username'] = str(user.username)
|
session['username'] = str(user.username)
|
||||||
else:
|
else:
|
||||||
|
@ -83,7 +82,7 @@ def login_auth():
|
||||||
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 = {}
|
||||||
user = User.query().by_username(session['username'])
|
user = User.query_().by_username(session['username'])
|
||||||
for auth_provider in AUTH_PROVIDER_LIST:
|
for auth_provider in AUTH_PROVIDER_LIST:
|
||||||
form = auth_provider.get_form()
|
form = auth_provider.get_form()
|
||||||
if auth_provider.get_name() not in session['auth_providers'] and\
|
if auth_provider.get_name() not in session['auth_providers'] and\
|
||||||
|
@ -95,31 +94,18 @@ def login_auth():
|
||||||
|
|
||||||
if len(session['auth_providers']) >= 2:
|
if len(session['auth_providers']) >= 2:
|
||||||
remember_me = True
|
remember_me = True
|
||||||
db_user = DbUser.query.filter(DbUser.username == session['username']).one_or_none()
|
# if db_user is None:
|
||||||
if db_user is None:
|
# db_user = User(username=session['username'])
|
||||||
db_user = DbUser(username=session['username'])
|
# db.session.add(db_user)
|
||||||
db.session.add(db_user)
|
# db.session.commit()
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
subject = db_user.id
|
subject = user.id
|
||||||
|
|
||||||
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,
|
||||||
'remember': remember_me})
|
'remember': remember_me})
|
||||||
return redirect(resp.redirect_to)
|
return redirect(resp.redirect_to)
|
||||||
|
|
||||||
login_user(SecurityUser(session['username']))
|
|
||||||
# TODO use this var
|
|
||||||
_next = None
|
|
||||||
try:
|
|
||||||
_next_url = urlparse(b64decode(request.args.get('next')).decode())
|
|
||||||
_host_url = urlparse(request.url)
|
|
||||||
if _next_url.scheme == _host_url.scheme and _next_url.netloc == _host_url.netloc :
|
|
||||||
_next = _next_url.geturl()
|
|
||||||
except TypeError:
|
|
||||||
_next = None
|
|
||||||
return redirect(_next or url_for('frontend.index'))
|
|
||||||
return render_template('auth/login_auth.html.j2', forms=auth_forms)
|
return render_template('auth/login_auth.html.j2', forms=auth_forms)
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,7 +114,7 @@ def logout():
|
||||||
logout_challenge = request.args.get('logout_challenge')
|
logout_challenge = request.args.get('logout_challenge')
|
||||||
logout_request = current_app.hydra_api.get_logout_request(logout_challenge)
|
logout_request = current_app.hydra_api.get_logout_request(logout_challenge)
|
||||||
resp = current_app.hydra_api.accept_logout_request(logout_challenge)
|
resp = current_app.hydra_api.accept_logout_request(logout_challenge)
|
||||||
logout_user()
|
# TODO confirm
|
||||||
return redirect(resp.redirect_to)
|
return redirect(resp.redirect_to)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,46 +1,47 @@
|
||||||
|
|
||||||
from urllib.parse import urlencode, parse_qs
|
from urllib.parse import urlencode, parse_qs
|
||||||
|
|
||||||
import flask
|
|
||||||
from flask import Blueprint, redirect
|
from flask import Blueprint, redirect
|
||||||
from flask import current_app, session
|
from flask import current_app
|
||||||
from flask import jsonify, send_file
|
from flask import jsonify
|
||||||
from flask.helpers import make_response
|
from flask import render_template, url_for, flash
|
||||||
from flask.templating import render_template
|
from flask_login import login_user, logout_user, current_user
|
||||||
from oic.oic.message import TokenErrorResponse, UserInfoErrorResponse, EndSessionRequest
|
|
||||||
|
|
||||||
from pyop.access_token import AccessToken, BearerTokenError
|
|
||||||
from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, InvalidClientAuthentication, OAuthError, \
|
|
||||||
InvalidSubjectIdentifier, InvalidClientRegistrationRequest
|
|
||||||
from pyop.util import should_fragment_encode
|
|
||||||
|
|
||||||
from flask import Blueprint, render_template, request, url_for, flash
|
|
||||||
from flask_login import login_required, login_user, logout_user, current_user
|
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import pyotp
|
from base64 import b64decode
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
from flask_dance.consumer import oauth_authorized
|
from flask_dance.consumer import oauth_authorized
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
|
||||||
from flask_dance.consumer import OAuth2ConsumerBlueprint
|
from flask_dance.consumer import OAuth2ConsumerBlueprint
|
||||||
|
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
|
||||||
|
|
||||||
from ..model import User, SecurityUser, Totp
|
from ..model import db, User, SecurityUser, Totp
|
||||||
from ..model_db import OAuth, db, User as DbUser
|
from ..form.frontend import ClientCertForm, TOTPForm, \
|
||||||
from ..form.frontend import ClientCertForm, TOTPForm, TOTPDeleteForm, PasswordChangeForm
|
TOTPDeleteForm, PasswordChangeForm
|
||||||
from ..auth_providers import AUTH_PROVIDER_LIST
|
from ..auth_providers import LdapAuthProvider
|
||||||
|
|
||||||
|
|
||||||
frontend_views = Blueprint('frontend', __name__, url_prefix='')
|
frontend_views = Blueprint('frontend', __name__, url_prefix='')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def before_request():
|
||||||
|
try:
|
||||||
|
resp = current_app.oauth.session.get('/userinfo')
|
||||||
|
if not current_user.is_authenticated:
|
||||||
|
return redirect(url_for('oauth.login'))
|
||||||
|
except TokenExpiredError:
|
||||||
|
return redirect(url_for('oauth.login'))
|
||||||
|
|
||||||
|
|
||||||
|
frontend_views.before_request(before_request)
|
||||||
|
|
||||||
|
|
||||||
def init_login_manager(app):
|
def init_login_manager(app):
|
||||||
@app.login_manager.user_loader
|
@app.login_manager.user_loader
|
||||||
def user_loader(username):
|
def user_loader(username):
|
||||||
return User.query().by_username(username)
|
return User.query_().by_username(username)
|
||||||
|
|
||||||
@app.login_manager.request_loader
|
@app.login_manager.request_loader
|
||||||
def request_loader(request):
|
def request_loader(_request):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@app.login_manager.unauthorized_handler
|
@app.login_manager.unauthorized_handler
|
||||||
|
@ -75,25 +76,9 @@ def init_login_manager(app):
|
||||||
|
|
||||||
oauth_info = resp.json()
|
oauth_info = resp.json()
|
||||||
|
|
||||||
db_user = DbUser.query.get(str(oauth_info["sub"]))
|
db_user = User.query.get(str(oauth_info["sub"]))
|
||||||
oauth_username = db_user.username
|
|
||||||
|
|
||||||
# Find this OAuth token in the database, or create it
|
login_user(SecurityUser(db_user.username))
|
||||||
query = OAuth.query.filter_by(
|
|
||||||
provider=blueprint.name,
|
|
||||||
provider_username=oauth_username,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
oauth = query.one()
|
|
||||||
except NoResultFound:
|
|
||||||
oauth = OAuth(
|
|
||||||
provider=blueprint.name,
|
|
||||||
provider_username=oauth_username,
|
|
||||||
token=token,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
login_user(SecurityUser(oauth.provider_username))
|
|
||||||
#flash("Successfully signed in with GitHub.")
|
#flash("Successfully signed in with GitHub.")
|
||||||
|
|
||||||
# Since we're manually creating the OAuth model in the database,
|
# Since we're manually creating the OAuth model in the database,
|
||||||
|
@ -106,27 +91,29 @@ def init_login_manager(app):
|
||||||
@frontend_views.route('/logout')
|
@frontend_views.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect(f'{current_app.config["HYDRA_PUBLIC_URL"]}/oauth2/sessions/logout')
|
return redirect(
|
||||||
|
f'{current_app.config["HYDRA_PUBLIC_URL"]}/oauth2/sessions/logout')
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/', methods=['GET'])
|
@frontend_views.route('/', methods=['GET'])
|
||||||
@login_required
|
|
||||||
def index():
|
def index():
|
||||||
return render_template('frontend/index.html.j2')
|
return render_template('frontend/index.html.j2')
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/client_cert')
|
@frontend_views.route('/client_cert')
|
||||||
@login_required
|
|
||||||
def client_cert():
|
def client_cert():
|
||||||
client_certs = {}
|
client_certs = {}
|
||||||
for service in current_app.lenticular_services.values():
|
for service in current_app.lenticular_services.values():
|
||||||
client_certs[str(service.name)] = current_app.pki.get_client_certs(current_user, service)
|
client_certs[str(service.name)] = \
|
||||||
|
current_app.pki.get_client_certs(current_user, service)
|
||||||
|
|
||||||
return render_template('frontend/client_cert.html.j2', services=current_app.lenticular_services, client_certs=client_certs)
|
return render_template(
|
||||||
|
'frontend/client_cert.html.j2',
|
||||||
|
services=current_app.lenticular_services,
|
||||||
|
client_certs=client_certs)
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/client_cert/<service_name>/<serial_number>')
|
@frontend_views.route('/client_cert/<service_name>/<serial_number>')
|
||||||
@login_required
|
|
||||||
def get_client_cert(service_name, serial_number):
|
def get_client_cert(service_name, serial_number):
|
||||||
service = current_app.lenticular_services[service_name]
|
service = current_app.lenticular_services[service_name]
|
||||||
cert = current_app.pki.get_client_cert(
|
cert = current_app.pki.get_client_cert(
|
||||||
|
@ -137,8 +124,8 @@ def get_client_cert(service_name, serial_number):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/client_cert/<service_name>/<serial_number>', methods=['DELETE'])
|
@frontend_views.route(
|
||||||
@login_required
|
'/client_cert/<service_name>/<serial_number>', methods=['DELETE'])
|
||||||
def revoke_client_cert(service_name, serial_number):
|
def revoke_client_cert(service_name, serial_number):
|
||||||
service = current_app.lenticular_services[service_name]
|
service = current_app.lenticular_services[service_name]
|
||||||
cert = current_app.pki.get_client_cert(
|
cert = current_app.pki.get_client_cert(
|
||||||
|
@ -150,7 +137,6 @@ def revoke_client_cert(service_name, serial_number):
|
||||||
@frontend_views.route(
|
@frontend_views.route(
|
||||||
'/client_cert/<service_name>/new',
|
'/client_cert/<service_name>/new',
|
||||||
methods=['GET', 'POST'])
|
methods=['GET', 'POST'])
|
||||||
@login_required
|
|
||||||
def client_cert_new(service_name):
|
def client_cert_new(service_name):
|
||||||
service = current_app.lenticular_services[service_name]
|
service = current_app.lenticular_services[service_name]
|
||||||
form = ClientCertForm()
|
form = ClientCertForm()
|
||||||
|
@ -180,23 +166,20 @@ def client_cert_new(service_name):
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/totp')
|
@frontend_views.route('/totp')
|
||||||
@login_required
|
|
||||||
def totp():
|
def totp():
|
||||||
delete_form = TOTPDeleteForm()
|
delete_form = TOTPDeleteForm()
|
||||||
return render_template('frontend/totp.html.j2', delete_form=delete_form)
|
return render_template('frontend/totp.html.j2', delete_form=delete_form)
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/totp/new', methods=['GET', 'POST'])
|
@frontend_views.route('/totp/new', methods=['GET', 'POST'])
|
||||||
@login_required
|
|
||||||
def totp_new():
|
def totp_new():
|
||||||
form = TOTPForm()
|
form = TOTPForm()
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
totp = Totp(name=form.data['name'], secret=form.data['secret'])
|
totp = Totp(name=form.data['name'], secret=form.data['secret'])
|
||||||
if totp.verify(form.data['token']):
|
if totp.verify(form.data['token']):
|
||||||
current_user.make_writeable()
|
|
||||||
current_user.totps.append(totp)
|
current_user.totps.append(totp)
|
||||||
current_user._ldap_object.entry_commit_changes()
|
db.session.commit()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'ok'})
|
'status': 'ok'})
|
||||||
else:
|
else:
|
||||||
|
@ -208,47 +191,55 @@ def totp_new():
|
||||||
return render_template('frontend/totp_new.html.j2', form=form)
|
return render_template('frontend/totp_new.html.j2', form=form)
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/totp/<totp_name>/delete', methods=['GET','POST'])
|
@frontend_views.route('/totp/<totp_name>/delete', methods=['GET', 'POST'])
|
||||||
@login_required
|
|
||||||
def totp_delete(totp_name):
|
def totp_delete(totp_name):
|
||||||
current_user.make_writeable()
|
totp = Totp.query.filter(Totp.name == totp_name).first()
|
||||||
current_user.totps.delete(totp_name)
|
db.session.delete(totp)
|
||||||
current_user._ldap_object.entry_commit_changes()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'ok'})
|
'status': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/password_change')
|
@frontend_views.route('/password_change')
|
||||||
@login_required
|
|
||||||
def password_change():
|
def password_change():
|
||||||
|
|
||||||
form = PasswordChangeForm()
|
form = PasswordChangeForm()
|
||||||
return render_template('frontend/password_change.html.j2', form=form)
|
return render_template('frontend/password_change.html.j2', form=form)
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/password_change', methods=['POST'])
|
@frontend_views.route('/password_change', methods=['POST'])
|
||||||
@login_required
|
|
||||||
def password_change_post():
|
def password_change_post():
|
||||||
form = PasswordChangeForm()
|
form = PasswordChangeForm()
|
||||||
if form.validate():
|
if form.validate():
|
||||||
|
password_old = str(form.data['password_old'])
|
||||||
|
password_new = str(form.data['password_new'])
|
||||||
|
if not LdapAuthProvider.check_auth_internal(
|
||||||
|
current_user, password_old):
|
||||||
|
return jsonify(
|
||||||
|
{'errors': {'password_old': 'Old Password is invalid'}})
|
||||||
|
resp = current_user.change_password(password_new)
|
||||||
|
if resp:
|
||||||
|
print(current_user)
|
||||||
return jsonify({})
|
return jsonify({})
|
||||||
|
else:
|
||||||
|
return jsonify({'errors': {'internal': 'internal server errror'}})
|
||||||
return jsonify({'errors': form.errors})
|
return jsonify({'errors': form.errors})
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/oauth2_token')
|
@frontend_views.route('/oauth2_token')
|
||||||
@login_required
|
|
||||||
def oauth2_tokens():
|
def oauth2_tokens():
|
||||||
|
|
||||||
subject = current_app.oauth.session.get('/userinfo').json()['sub']
|
subject = current_app.oauth.session.get('/userinfo').json()['sub']
|
||||||
consent_sessions = current_app.hydra_api.list_subject_consent_sessions(
|
consent_sessions = current_app.hydra_api.list_subject_consent_sessions(
|
||||||
subject)
|
subject)
|
||||||
|
|
||||||
return render_template('frontend/oauth2_tokens.html.j2', consent_sessions=consent_sessions)
|
print(consent_sessions)
|
||||||
|
return render_template(
|
||||||
|
'frontend/oauth2_tokens.html.j2',
|
||||||
|
consent_sessions=consent_sessions)
|
||||||
|
|
||||||
|
|
||||||
@frontend_views.route('/oauth2_token/<client_id>', methods=['DELETE'])
|
@frontend_views.route('/oauth2_token/<client_id>', methods=['DELETE'])
|
||||||
@login_required
|
|
||||||
def oauth2_token_revoke(client_id: str):
|
def oauth2_token_revoke(client_id: str):
|
||||||
subject = current_app.oauth.session.get('/userinfo').json()['sub']
|
subject = current_app.oauth.session.get('/userinfo').json()['sub']
|
||||||
current_app.hydra_api.revoke_consent_sessions(
|
current_app.hydra_api.revoke_consent_sessions(
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -18,18 +18,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if current_user.is_authenticated %}
|
|
||||||
<nav class="col-md-2 d-none d-md-block bg-light sidebar fixed-top">
|
|
||||||
<div class="sidebar-sticky active">
|
|
||||||
{#<a href="/"><img alt="logo" class="container-fluid" src="/static/images/dog_main_small.png"></a>#}
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.index') }}">{{ gettext('Account') }}</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.client_cert') }}">{{ gettext('Client Cert') }}</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.totp') }}">{{ gettext('2FA - TOTP') }}</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('frontend.logout') }}">{{ gettext('Logout') }}</a></li>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<main class="col-md-9 ml-sm-auto col-lg-10 px-4" role="main">
|
<main class="col-md-9 ml-sm-auto col-lg-10 px-4" role="main">
|
||||||
<h1>{% block title %}{% endblock %}</h1>
|
<h1>{% block title %}{% endblock %}</h1>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
|
@ -6,4 +6,11 @@
|
||||||
|
|
||||||
{{ render_form(form)}}
|
{{ render_form(form)}}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script_js %}
|
||||||
|
|
||||||
|
password_change.init();
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue