working oidc

This commit is contained in:
Daniel Tsvetkov 2021-05-09 11:16:22 +02:00
parent 01d1ac23de
commit 44b409d886
3 changed files with 50 additions and 21 deletions

View File

@ -194,18 +194,18 @@ class User(db.Model, ModelController, UserMixin):
backref=db.backref('users', lazy='dynamic')) backref=db.backref('users', lazy='dynamic'))
class U2FCredential(db.Model, ModelController): class Credential(db.Model, ModelController):
name = db.Column(db.Unicode) name = db.Column(db.Unicode)
date_added = db.Column(db.DateTime) date_added = db.Column(db.DateTime)
device = db.Column(db.Unicode) device = db.Column(db.Unicode)
user_sso_id = db.Column(db.Integer, db.ForeignKey('user_sso.id')) user_sso_id = db.Column(db.Integer, db.ForeignKey('user_s.id'))
user_sso = db.relationship('UserSSO', user_sso = db.relationship('UserS',
backref=db.backref("u2f_credentials"), backref=db.backref("credentials"),
) )
class UserSSO(db.Model, ModelController): class UserS(db.Model, ModelController):
username = db.Column(db.Unicode, unique=True) username = db.Column(db.Unicode, unique=True)
email = db.Column(db.Unicode, unique=True) email = db.Column(db.Unicode, unique=True)
password_hash = db.Column(db.Unicode) password_hash = db.Column(db.Unicode)
@ -213,7 +213,7 @@ class UserSSO(db.Model, ModelController):
otp_secret = db.Column(db.String(16)) otp_secret = db.Column(db.String(16))
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(UserSSO, self).__init__(**kwargs) super(UserS, self).__init__(**kwargs)
if self.otp_secret is None: if self.otp_secret is None:
# generate a random secret # generate a random secret
self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8') self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')

View File

@ -1,4 +1,6 @@
import re import re
import random
import string
def camel_case_to_snake_case(name): def camel_case_to_snake_case(name):
@ -17,4 +19,11 @@ def snake_case_to_camel_case(name):
:param name: the name to be converted :param name: the name to be converted
:return: :return:
""" """
return ''.join(x.title() for x in name.split('_')) return ''.join(x.title() for x in name.split('_'))
def random_string_generator(str_size=30, allowed_chars=None):
if not allowed_chars:
allowed_chars = string.ascii_letters + string.digits
return ''.join(random.choice(allowed_chars) for _ in range(str_size))

View File

@ -1,8 +1,9 @@
import urllib import urllib
import requests import requests
from flask import send_from_directory, redirect, request, url_for from flask import send_from_directory, redirect, request, url_for, session, jsonify
from oshipka.util.strings import random_string_generator
from oshipka.webapp import oshipka_bp from oshipka.webapp import oshipka_bp
from config import MEDIA_DIR, APP_BASE_URL from config import MEDIA_DIR, APP_BASE_URL
from sensitive import SSO_CLIENT_ID, SSO_CLIENT_SECRET from sensitive import SSO_CLIENT_ID, SSO_CLIENT_SECRET
@ -15,37 +16,56 @@ def get_media(filepath):
SSO_BASE_URL = 'http://localhost:5008' SSO_BASE_URL = 'http://localhost:5008'
SSO_AUTH_URL = '/oidc/auth'
SSO_TOKEN_URL = '/oidc/token'
SSO_USERINFO_URL = "/endpoints/userinfo"
@oshipka_bp.route('/sso') @oshipka_bp.route('/sso')
def sso(): def sso():
callback_url = APP_BASE_URL + url_for('oshipka_bp.oidc_code') callback_url = APP_BASE_URL + url_for('oshipka_bp.oidc_callback')
return redirect(SSO_BASE_URL + '/authenticate?callback={}&client_id={}'.format( state = random_string_generator()
urllib.parse.quote(callback_url), session['oidc_state'] = state
SSO_CLIENT_ID, params = urllib.parse.urlencode({
)) 'redirect_uri': callback_url,
'client_id': SSO_CLIENT_ID,
'state': state,
'scope': 'openid',
'response_type': 'code',
'nonce': random_string_generator(),
})
return redirect(SSO_BASE_URL + SSO_AUTH_URL + '?' + params)
@oshipka_bp.route('/oidc/code') @oshipka_bp.route('/oidc/callback')
def oidc_code(): def oidc_callback():
error = request.args.get('error')
if error:
return jsonify({"error": "from auth server: {}".format(error)}), 400
state = request.args.get('state')
session_state = session['oidc_state']
if state != session_state:
return jsonify({"error": "state is different from session state"}), 400
code = request.args.get('code') code = request.args.get('code')
# TODO : client_id and client_secret are passed in Authorization header response = requests.post(
# https://connect2id.com/learn/openid-connect SSO_BASE_URL + SSO_TOKEN_URL,
response = requests.get( data={
SSO_BASE_URL + "/oidc/token",
params={
'code': code, 'code': code,
'client_id': SSO_CLIENT_ID, 'client_id': SSO_CLIENT_ID,
'client_secret': SSO_CLIENT_SECRET, 'client_secret': SSO_CLIENT_SECRET,
'grant_type': 'authorization_code'
}, },
) )
if response.status_code == 200: if response.status_code == 200:
response_json = response.json() response_json = response.json()
access_token = response_json.get('access_token') access_token = response_json.get('access_token')
response = requests.get( response = requests.get(
SSO_BASE_URL + "/endpoints/user", SSO_BASE_URL + SSO_USERINFO_URL,
headers={ headers={
'Authorization': "Bearer {}".format(access_token) 'Authorization': "Bearer {}".format(access_token)
}, },
) )
if response.status_code == 200:
return response.json()
return 'got code for userinfo: {}'.format(response.status_code)
return 'got response for token: {}'.format(response.status_code) return 'got response for token: {}'.format(response.status_code)