Skip to content

Commit eb3941d

Browse files
Merge pull request #9 from parallelsystems/add-azure-sso
Add azure sso
2 parents 67554c0 + dd6bda3 commit eb3941d

File tree

15 files changed

+2094
-42
lines changed

15 files changed

+2094
-42
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ raven = {extras = ["flask"], version = "*"}
4040
redis = "*"
4141
watchdog = "*"
4242
weber_utils = "*"
43+
msal = "*"
4344

4445
[dev-packages]
4546
Flask-Loopback = "*"

Pipfile.lock

Lines changed: 296 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:8 as frontend-builder
1+
FROM node:10 as frontend-builder
22
# build frontend
33
RUN npm install -g ember-cli
44

flask_app/auth.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from .config import get_runtime_config_private_dict
1414
from .models import db, Role, User
15-
from .utils.oauth2 import get_oauth2_identity
15+
from .utils.oauth2 import get_oauth2_identity, get_oauth2_identity_azure
1616

1717
_logger = logbook.Logger(__name__)
1818

@@ -52,9 +52,15 @@ def login():
5252
return _login_with_credentials(credentials)
5353

5454
auth_code = credentials.get('authorizationCode')
55-
if auth_code:
55+
provider = credentials.get("provider")
56+
redirect_uri = credentials.get("redirectUri")
57+
58+
if provider == "google-oauth2":
5659
return _login_with_google_oauth2(auth_code)
5760

61+
if provider == "azure-ad2-oauth2":
62+
return _login_with_azure_oauth2(auth_code, redirect_uri)
63+
5864
error_abort('No credentials were specified', code=requests.codes.unauthorized)
5965

6066

@@ -137,6 +143,19 @@ def _login_with_google_oauth2(auth_code):
137143

138144
return _make_success_login_response(user, user_info)
139145

146+
def _login_with_azure_oauth2(auth_code, redirect_uri):
147+
"""Logs in with azure oath2"""
148+
user_info = get_oauth2_identity_azure(auth_code, redirect_uri)
149+
if not user_info:
150+
error_abort('Could not complete OAuth2 exchange', code=requests.codes.unauthorized)
151+
152+
_check_alowed_email_domain(user_info)
153+
154+
user = get_or_create_user(user_info)
155+
login_user(user)
156+
157+
return _make_success_login_response(user, user_info)
158+
140159

141160
@auth.route("/logout", methods=['POST'])
142161
def logout():

flask_app/blueprints/api/setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def get_app_config():
2727
'google_oauth2_enabled': False,
2828
'google_oauth2_client_id': None,
2929
'google_oauth2_client_secret': None,
30+
'azure_oauth2_enabled': False,
31+
'azure_oauth2_client_id': None,
32+
'azure_oauth2_client_secret': None,
33+
'azure_oauth2_tenant_id': None,
3034
'ldap_login_enabled': False,
3135
'ldap_uri': None,
3236
'ldap_base_dn': None,

flask_app/utils/oauth2.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from httplib2 import Http
55
from apiclient.discovery import build
66
from oauth2client.client import OAuth2WebServerFlow
7+
import msal
78

89
from .. import config
910

@@ -41,6 +42,42 @@ def get_oauth2_identity(auth_code):
4142
_logger.debug('Found user info: {}', info)
4243
return info
4344

45+
def get_oauth2_identity_azure(auth_code, redirect_uri):
46+
"""Gets identity from azure auth_code"""
47+
48+
config_dict = config.get_runtime_config_private_dict()
49+
client_id = config_dict['azure_oauth2_client_id']
50+
client_secret = config_dict['azure_oauth2_client_secret']
51+
authority = f"https://login.microsoftonline.com/{config_dict['azure_oauth2_tenant_id']}"
52+
53+
if not client_id:
54+
_logger.error('No OAuth2 client id configured')
55+
return
56+
57+
if not client_secret:
58+
_logger.error('No OAuth2 client secret configured')
59+
return
60+
61+
_logger.info('get_oauth2_identity: Using redirect URI {!r}', redirect_uri)
62+
63+
client = msal.ConfidentialClientApplication(
64+
client_id, authority=authority,
65+
client_credential=client_secret, token_cache=None)
66+
67+
token = client.acquire_token_by_authorization_code(code=auth_code, scopes=["User.read"], redirect_uri=redirect_uri)
68+
69+
if "error" in token:
70+
_logger.error(token["error_description"])
71+
assert False
72+
73+
user_info = token["id_token_claims"]
74+
75+
return {
76+
"email" : user_info["email"],
77+
"first_name" : user_info["name"].split()[0],
78+
"last_name" : user_info["name"].split()[-1],
79+
}
80+
4481

4582
def _get_user_info(credentials):
4683
http_client = Http()

manage.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ def _ensure_conf():
130130
if not os.path.isfile(private_filename):
131131
with open(private_filename, 'w') as f:
132132
for secret_name in ('SECRET_KEY', 'SECURITY_PASSWORD_SALT'):
133-
f.write('{}: {!r}\n'.format(secret_name, _generate_secret_string()))
133+
# either pull value from environment or generate random value
134+
f.write('{}: {!r}\n'.format(secret_name, os.environ.get(secret_name, _generate_secret_string())))
134135

135136
def _generate_secret_string(length=50):
136137
return "".join([random.choice(string.ascii_letters) for i in range(length)])

webapp/app/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ const App = Application.extend({
1111

1212
loadInitializers(App, config.modulePrefix);
1313
config.torii.providers["google-oauth2"].redirectUri = window.location.origin;
14+
config.torii.providers["azure-ad2-oauth2"].redirectUri = window.location.origin;
1415
export default App;

webapp/app/application/route.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export default Route.extend(ApplicationRouteMixin, {
2929
let cfg = config.torii;
3030
cfg.providers["google-oauth2"].apiKey =
3131
model.runtime_config.google_oauth2_client_id;
32+
cfg.providers["azure-ad2-oauth2"].apiKey =
33+
model.runtime_config.azure_oauth2_client_id;
34+
cfg.providers["azure-ad2-oauth2"].tenantId =
35+
model.runtime_config.azure_oauth2_tenant_id;
36+
3237
this.load_current_user();
3338
}
3439
},

webapp/app/login/controller.js

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export default Controller.extend(UnauthenticatedRouteMixin, {
1414
self.set("login_error", null);
1515
const credentials = this.getProperties(["username", "password"]);
1616
self.get("session").authenticate("authenticator:token", credentials).then(
17-
function() {},
18-
function() {
17+
function () { },
18+
function () {
1919
self.set("login_error", "Invalid username and/or password");
2020
}
2121
);
@@ -28,24 +28,53 @@ export default Controller.extend(UnauthenticatedRouteMixin, {
2828
self
2929
.get("torii")
3030
.open("google-oauth2")
31-
.then(function(auth) {
31+
.then(function (auth) {
3232
return self
33+
3334
.get("session")
3435
.authenticate("authenticator:token", auth)
3536
.then(
36-
function(data) {
37+
function (data) {
3738
return data;
3839
},
39-
function(error) {
40+
function (error) {
4041
self.set("login_error", error.error);
4142
}
4243
);
4344
})
44-
.finally(function() {
45+
.finally(function () {
46+
self.set("loading", false);
47+
});
48+
49+
return;
50+
},
51+
52+
login_azure() {
53+
let self = this;
54+
self.set("loading", true);
55+
self.set("login_error", null);
56+
self
57+
.get("torii")
58+
.open("azure-ad2-oauth2", { "response_type": "id_token+code" })
59+
.then(function (auth) {
60+
return self
61+
.get("session")
62+
.authenticate("authenticator:token", auth)
63+
.then(
64+
function (data) {
65+
return data;
66+
},
67+
function (error) {
68+
self.set("login_error", error.error);
69+
}
70+
);
71+
})
72+
.finally(function () {
4573
self.set("loading", false);
4674
});
4775

4876
return;
4977
}
78+
5079
}
5180
});

0 commit comments

Comments
 (0)