Skip to content

Commit

Permalink
Merge pull request #9 from parallelsystems/add-azure-sso
Browse files Browse the repository at this point in the history
Add azure sso
  • Loading branch information
christosachilleoudis authored Jul 28, 2022
2 parents 67554c0 + dd6bda3 commit eb3941d
Show file tree
Hide file tree
Showing 15 changed files with 2,094 additions and 42 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ raven = {extras = ["flask"], version = "*"}
redis = "*"
watchdog = "*"
weber_utils = "*"
msal = "*"

[dev-packages]
Flask-Loopback = "*"
Expand Down
322 changes: 296 additions & 26 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:8 as frontend-builder
FROM node:10 as frontend-builder
# build frontend
RUN npm install -g ember-cli

Expand Down
23 changes: 21 additions & 2 deletions flask_app/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from .config import get_runtime_config_private_dict
from .models import db, Role, User
from .utils.oauth2 import get_oauth2_identity
from .utils.oauth2 import get_oauth2_identity, get_oauth2_identity_azure

_logger = logbook.Logger(__name__)

Expand Down Expand Up @@ -52,9 +52,15 @@ def login():
return _login_with_credentials(credentials)

auth_code = credentials.get('authorizationCode')
if auth_code:
provider = credentials.get("provider")
redirect_uri = credentials.get("redirectUri")

if provider == "google-oauth2":
return _login_with_google_oauth2(auth_code)

if provider == "azure-ad2-oauth2":
return _login_with_azure_oauth2(auth_code, redirect_uri)

error_abort('No credentials were specified', code=requests.codes.unauthorized)


Expand Down Expand Up @@ -137,6 +143,19 @@ def _login_with_google_oauth2(auth_code):

return _make_success_login_response(user, user_info)

def _login_with_azure_oauth2(auth_code, redirect_uri):
"""Logs in with azure oath2"""
user_info = get_oauth2_identity_azure(auth_code, redirect_uri)
if not user_info:
error_abort('Could not complete OAuth2 exchange', code=requests.codes.unauthorized)

_check_alowed_email_domain(user_info)

user = get_or_create_user(user_info)
login_user(user)

return _make_success_login_response(user, user_info)


@auth.route("/logout", methods=['POST'])
def logout():
Expand Down
4 changes: 4 additions & 0 deletions flask_app/blueprints/api/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def get_app_config():
'google_oauth2_enabled': False,
'google_oauth2_client_id': None,
'google_oauth2_client_secret': None,
'azure_oauth2_enabled': False,
'azure_oauth2_client_id': None,
'azure_oauth2_client_secret': None,
'azure_oauth2_tenant_id': None,
'ldap_login_enabled': False,
'ldap_uri': None,
'ldap_base_dn': None,
Expand Down
37 changes: 37 additions & 0 deletions flask_app/utils/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from httplib2 import Http
from apiclient.discovery import build
from oauth2client.client import OAuth2WebServerFlow
import msal

from .. import config

Expand Down Expand Up @@ -41,6 +42,42 @@ def get_oauth2_identity(auth_code):
_logger.debug('Found user info: {}', info)
return info

def get_oauth2_identity_azure(auth_code, redirect_uri):
"""Gets identity from azure auth_code"""

config_dict = config.get_runtime_config_private_dict()
client_id = config_dict['azure_oauth2_client_id']
client_secret = config_dict['azure_oauth2_client_secret']
authority = f"https://login.microsoftonline.com/{config_dict['azure_oauth2_tenant_id']}"

if not client_id:
_logger.error('No OAuth2 client id configured')
return

if not client_secret:
_logger.error('No OAuth2 client secret configured')
return

_logger.info('get_oauth2_identity: Using redirect URI {!r}', redirect_uri)

client = msal.ConfidentialClientApplication(
client_id, authority=authority,
client_credential=client_secret, token_cache=None)

token = client.acquire_token_by_authorization_code(code=auth_code, scopes=["User.read"], redirect_uri=redirect_uri)

if "error" in token:
_logger.error(token["error_description"])
assert False

user_info = token["id_token_claims"]

return {
"email" : user_info["email"],
"first_name" : user_info["name"].split()[0],
"last_name" : user_info["name"].split()[-1],
}


def _get_user_info(credentials):
http_client = Http()
Expand Down
3 changes: 2 additions & 1 deletion manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ def _ensure_conf():
if not os.path.isfile(private_filename):
with open(private_filename, 'w') as f:
for secret_name in ('SECRET_KEY', 'SECURITY_PASSWORD_SALT'):
f.write('{}: {!r}\n'.format(secret_name, _generate_secret_string()))
# either pull value from environment or generate random value
f.write('{}: {!r}\n'.format(secret_name, os.environ.get(secret_name, _generate_secret_string())))

def _generate_secret_string(length=50):
return "".join([random.choice(string.ascii_letters) for i in range(length)])
Expand Down
1 change: 1 addition & 0 deletions webapp/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ const App = Application.extend({

loadInitializers(App, config.modulePrefix);
config.torii.providers["google-oauth2"].redirectUri = window.location.origin;
config.torii.providers["azure-ad2-oauth2"].redirectUri = window.location.origin;
export default App;
5 changes: 5 additions & 0 deletions webapp/app/application/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export default Route.extend(ApplicationRouteMixin, {
let cfg = config.torii;
cfg.providers["google-oauth2"].apiKey =
model.runtime_config.google_oauth2_client_id;
cfg.providers["azure-ad2-oauth2"].apiKey =
model.runtime_config.azure_oauth2_client_id;
cfg.providers["azure-ad2-oauth2"].tenantId =
model.runtime_config.azure_oauth2_tenant_id;

this.load_current_user();
}
},
Expand Down
41 changes: 35 additions & 6 deletions webapp/app/login/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export default Controller.extend(UnauthenticatedRouteMixin, {
self.set("login_error", null);
const credentials = this.getProperties(["username", "password"]);
self.get("session").authenticate("authenticator:token", credentials).then(
function() {},
function() {
function () { },
function () {
self.set("login_error", "Invalid username and/or password");
}
);
Expand All @@ -28,24 +28,53 @@ export default Controller.extend(UnauthenticatedRouteMixin, {
self
.get("torii")
.open("google-oauth2")
.then(function(auth) {
.then(function (auth) {
return self

.get("session")
.authenticate("authenticator:token", auth)
.then(
function(data) {
function (data) {
return data;
},
function(error) {
function (error) {
self.set("login_error", error.error);
}
);
})
.finally(function() {
.finally(function () {
self.set("loading", false);
});

return;
},

login_azure() {
let self = this;
self.set("loading", true);
self.set("login_error", null);
self
.get("torii")
.open("azure-ad2-oauth2", { "response_type": "id_token+code" })
.then(function (auth) {
return self
.get("session")
.authenticate("authenticator:token", auth)
.then(
function (data) {
return data;
},
function (error) {
self.set("login_error", error.error);
}
);
})
.finally(function () {
self.set("loading", false);
});

return;
}

}
});
40 changes: 40 additions & 0 deletions webapp/app/login/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,45 @@
</div>
{{/if}}

{{#if runtime_config.azure_oauth2_enabled}}
<p class="text-center">
Or
</p>

<div class="form-group">
<button class="btn btn-lg btn-outline-secondary w-100" {{action "login_azure" }}>
<svg id="bdb56329-4717-4410-aa13-4505ecaa4e46" xmlns="http://www.w3.org/2000/svg" width="18" height="18"
viewBox="0 0 18 18">
<defs>
<linearGradient id="ba2610c3-a45a-4e7e-a0c0-285cfd7e005d" x1="13.25" y1="13.02" x2="8.62"
y2="4.25" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1988d9" />
<stop offset="0.9" stop-color="#54aef0" />
</linearGradient>
<linearGradient id="bd8f618b-4f2f-4cb7-aff0-2fd2d211326d" x1="11.26" y1="10.47" x2="14.46"
y2="15.99" gradientUnits="userSpaceOnUse">
<stop offset="0.1" stop-color="#54aef0" />
<stop offset="0.29" stop-color="#4fabee" />
<stop offset="0.51" stop-color="#41a2e9" />
<stop offset="0.74" stop-color="#2a93e0" />
<stop offset="0.88" stop-color="#1988d9" />
</linearGradient>
</defs>
<title>Icon-identity-221</title>
<polygon points="1.01 10.19 8.93 15.33 16.99 10.17 18 11.35 8.93 17.19 0 11.35 1.01 10.19"
fill="#50e6ff" />
<polygon points="1.61 9.53 8.93 0.81 16.4 9.54 8.93 14.26 1.61 9.53" fill="#fff" />
<polygon points="8.93 0.81 8.93 14.26 1.61 9.53 8.93 0.81" fill="#50e6ff" />
<polygon points="8.93 0.81 8.93 14.26 16.4 9.54 8.93 0.81"
fill="url(#ba2610c3-a45a-4e7e-a0c0-285cfd7e005d)" />
<polygon points="8.93 7.76 16.4 9.54 8.93 14.26 8.93 7.76" fill="#53b1e0" />
<polygon points="8.93 14.26 1.61 9.53 8.93 7.76 8.93 14.26" fill="#9cebff" />
<polygon points="8.93 17.19 18 11.35 16.99 10.17 8.93 15.33 8.93 17.19"
fill="url(#bd8f618b-4f2f-4cb7-aff0-2fd2d211326d)" />
</svg> Sign in with Azure AD
</button>
</div>
{{/if}}

</div>
</div>
31 changes: 31 additions & 0 deletions webapp/app/setup/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,37 @@
{{/if}}


</div>

<div class="form-group">

<div class="checkbox">
<label>
{{input type="checkbox" id="social-azure-enable" checked=config.azure_oauth2_enabled}}
Enable Azure AD OAuth2 Authentication
</label>
</div>

{{#if config.azure_oauth2_enabled}}
<div class="form-group">
<label class="control-label" for="azure-oauth2-client-id">OAuth2 Client ID</label>
{{input class="form-control" id="azure-oauth2-client-id" value=config.azure_oauth2_client_id placeholder="XXXXXX" required=true}}
</div>

<div class="form-group">
<label class="control-label" for="azure-oauth2-client-secret">OAuth2 Client Secret</label>
{{input class="form-control" id="azure-oauth2-client-secret" value=config.azure_oauth2_client_secret placeholder="XXXXXX" required=true}}
</div>

<div class="form-group">
<label class="control-label" for="azure-oauth2-tenant-id">OAuth2 Tenant ID</label>
{{input class="form-control" id="azure-oauth2-tenant-id" value=config.azure_oauth2_tenant_id placeholder="XXXXXX" required=true}}
</div>

<p class="help-block">Can be obtained from <a href="https://portal.azure.com/">the Azure Developer Console</a></p>
{{/if}}


</div>

<div class="form-group">
Expand Down
13 changes: 10 additions & 3 deletions webapp/config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,16 @@ module.exports = function(environment) {
'google-oauth2': {
// redirectUri is assigned in app.js...
apiKey: null,
scope: 'email profile'
}
}
scope: 'email profile',
allowUnsafeRedirect: true
},
'azure-ad2-oauth2': {
tenantId: null,
apiKey: null,
scope: "openid profile email user.read",
allowUnsafeRedirect: true,
}
}
}
};

Expand Down
1 change: 1 addition & 0 deletions webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"sass": "^1.17.3",
"showdown": "^1.6.4",
"torii": "^0.10.1",
"torii-azure-ad2-provider": "^1.0.5",
"twix": "^1.2.1"
},
"engines": {
Expand Down
Loading

0 comments on commit eb3941d

Please sign in to comment.