... | ... |
@@ -8,13 +8,22 @@ |
8 | 8 |
* Controller of the openshiftConsole |
9 | 9 |
*/ |
10 | 10 |
angular.module('openshiftConsole') |
11 |
- .controller('OAuthController', function ($location, $q, RedirectLoginService, DataService, AuthService, Logger) { |
|
11 |
+ .controller('OAuthController', function ($scope, $location, $q, RedirectLoginService, DataService, AuthService, Logger) { |
|
12 | 12 |
var authLogger = Logger.get("auth"); |
13 | 13 |
|
14 |
+ // Initialize to a no-op function. |
|
15 |
+ // Needed to let the view confirm a login when the state is unverified. |
|
16 |
+ $scope.completeLogin = function(){}; |
|
17 |
+ $scope.cancelLogin = function() { |
|
18 |
+ $location.replace(); |
|
19 |
+ $location.url("./"); |
|
20 |
+ }; |
|
21 |
+ |
|
14 | 22 |
RedirectLoginService.finish() |
15 | 23 |
.then(function(data) { |
16 | 24 |
var token = data.token; |
17 | 25 |
var then = data.then; |
26 |
+ var verified = data.verified; |
|
18 | 27 |
var ttl = data.ttl; |
19 | 28 |
|
20 | 29 |
// Try to fetch the user |
... | ... |
@@ -25,21 +34,41 @@ angular.module('openshiftConsole') |
25 | 25 |
.then(function(user) { |
26 | 26 |
// Set the new user and token in the auth service |
27 | 27 |
authLogger.log("OAuthController, got user", user); |
28 |
- AuthService.setUser(user, token, ttl); |
|
29 | 28 |
|
30 |
- // Redirect to original destination (or default to '/') |
|
31 |
- var destination = then || './'; |
|
32 |
- if (URI(destination).is('absolute')) { |
|
33 |
- authLogger.log("OAuthController, invalid absolute redirect", destination); |
|
34 |
- destination = './'; |
|
29 |
+ $scope.completeLogin = function() { |
|
30 |
+ // Persist the user |
|
31 |
+ AuthService.setUser(user, token, ttl); |
|
32 |
+ |
|
33 |
+ // Redirect to original destination (or default to './') |
|
34 |
+ var destination = then || './'; |
|
35 |
+ if (URI(destination).is('absolute')) { |
|
36 |
+ authLogger.log("OAuthController, invalid absolute redirect", destination); |
|
37 |
+ destination = './'; |
|
38 |
+ } |
|
39 |
+ authLogger.log("OAuthController, redirecting", destination); |
|
40 |
+ $location.replace(); |
|
41 |
+ $location.url(destination); |
|
42 |
+ }; |
|
43 |
+ |
|
44 |
+ if (verified) { |
|
45 |
+ // Automatically complete |
|
46 |
+ $scope.completeLogin(); |
|
47 |
+ } else { |
|
48 |
+ // Require the UI to prompt |
|
49 |
+ $scope.confirmUser = user; |
|
50 |
+ |
|
51 |
+ // Additionally, give the UI info about the user being overridden |
|
52 |
+ var currentUser = AuthService.UserStore().getUser(); |
|
53 |
+ if (currentUser && currentUser.metadata.name !== user.metadata.name) { |
|
54 |
+ $scope.overriddenUser = currentUser; |
|
55 |
+ } |
|
35 | 56 |
} |
36 |
- authLogger.log("OAuthController, redirecting", destination); |
|
37 |
- $location.url(destination); |
|
38 | 57 |
}) |
39 | 58 |
.catch(function(rejection) { |
40 | 59 |
// Handle an API error response fetching the user |
41 | 60 |
var redirect = URI('error').query({error: 'user_fetch_failed'}).toString(); |
42 | 61 |
authLogger.error("OAuthController, error fetching user", rejection, "redirecting", redirect); |
62 |
+ $location.replace(); |
|
43 | 63 |
$location.url(redirect); |
44 | 64 |
}); |
45 | 65 |
|
... | ... |
@@ -51,6 +80,7 @@ angular.module('openshiftConsole') |
51 | 51 |
error_uri: rejection.error_uri || "" |
52 | 52 |
}).toString(); |
53 | 53 |
authLogger.error("OAuthController, error", rejection, "redirecting", redirect); |
54 |
+ $location.replace(); |
|
54 | 55 |
$location.url(redirect); |
55 | 56 |
}); |
56 | 57 |
|
... | ... |
@@ -29,6 +29,70 @@ angular.module('openshiftConsole') |
29 | 29 |
this.$get = function($location, $q, Logger) { |
30 | 30 |
var authLogger = Logger.get("auth"); |
31 | 31 |
|
32 |
+ var getRandomInts = function(length) { |
|
33 |
+ var randomValues; |
|
34 |
+ |
|
35 |
+ if (window.crypto && window.Uint32Array) { |
|
36 |
+ try { |
|
37 |
+ var r = new Uint32Array(length); |
|
38 |
+ window.crypto.getRandomValues(r); |
|
39 |
+ randomValues = []; |
|
40 |
+ for (var j=0; j < length; j++) { |
|
41 |
+ randomValues.push(r[j]); |
|
42 |
+ } |
|
43 |
+ } catch(e) { |
|
44 |
+ authLogger.debug("RedirectLoginService.getRandomInts: ", e); |
|
45 |
+ randomValues = null; |
|
46 |
+ } |
|
47 |
+ } |
|
48 |
+ |
|
49 |
+ if (!randomValues) { |
|
50 |
+ randomValues = []; |
|
51 |
+ for (var i=0; i < length; i++) { |
|
52 |
+ randomValues.push(Math.floor(Math.random() * 4294967296)); |
|
53 |
+ } |
|
54 |
+ } |
|
55 |
+ |
|
56 |
+ return randomValues; |
|
57 |
+ }; |
|
58 |
+ |
|
59 |
+ var nonceKey = "RedirectLoginService.nonce"; |
|
60 |
+ var makeState = function(then) { |
|
61 |
+ var nonce = String(new Date().getTime()) + "-" + getRandomInts(8).join(""); |
|
62 |
+ try { |
|
63 |
+ window.localStorage[nonceKey] = nonce; |
|
64 |
+ } catch(e) { |
|
65 |
+ authLogger.log("RedirectLoginService.makeState, localStorage error: ", e); |
|
66 |
+ } |
|
67 |
+ return JSON.stringify({then: then, nonce:nonce}); |
|
68 |
+ }; |
|
69 |
+ var parseState = function(state) { |
|
70 |
+ var retval = { |
|
71 |
+ then: null, |
|
72 |
+ verified: false |
|
73 |
+ }; |
|
74 |
+ |
|
75 |
+ var nonce = ""; |
|
76 |
+ try { |
|
77 |
+ nonce = window.localStorage[nonceKey]; |
|
78 |
+ window.localStorage.removeItem(nonceKey); |
|
79 |
+ } catch(e) { |
|
80 |
+ authLogger.log("RedirectLoginService.parseState, localStorage error: ", e); |
|
81 |
+ } |
|
82 |
+ |
|
83 |
+ try { |
|
84 |
+ var data = state ? JSON.parse(state) : {}; |
|
85 |
+ if (data && data.nonce && nonce && data.nonce === nonce) { |
|
86 |
+ retval.verified = true; |
|
87 |
+ retval.then = data.then; |
|
88 |
+ } |
|
89 |
+ } catch(e) { |
|
90 |
+ authLogger.error("RedirectLoginService.parseState, state error: ", e); |
|
91 |
+ } |
|
92 |
+ authLogger.error("RedirectLoginService.parseState", retval); |
|
93 |
+ return retval; |
|
94 |
+ }; |
|
95 |
+ |
|
32 | 96 |
return { |
33 | 97 |
// Returns a promise that resolves with {user:{...}, token:'...', ttl:X}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']} |
34 | 98 |
login: function() { |
... | ... |
@@ -49,7 +113,7 @@ angular.module('openshiftConsole') |
49 | 49 |
uri.query({ |
50 | 50 |
client_id: _oauth_client_id, |
51 | 51 |
response_type: 'token', |
52 |
- state: returnUri.toString(), |
|
52 |
+ state: makeState(returnUri.toString()), |
|
53 | 53 |
redirect_uri: _oauth_redirect_uri |
54 | 54 |
}); |
55 | 55 |
authLogger.log("RedirectLoginService.login(), redirecting", uri.toString()); |
... | ... |
@@ -59,7 +123,7 @@ angular.module('openshiftConsole') |
59 | 59 |
}, |
60 | 60 |
|
61 | 61 |
// Parses oauth callback parameters from window.location |
62 |
- // Returns a promise that resolves with {token:'...',then:'...'}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']} |
|
62 |
+ // Returns a promise that resolves with {token:'...',then:'...',verified:true|false}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']} |
|
63 | 63 |
// If no token and no error is present, resolves with {} |
64 | 64 |
// Example error codes: https://tools.ietf.org/html/rfc6749#section-5.2 |
65 | 65 |
finish: function() { |
... | ... |
@@ -71,12 +135,12 @@ angular.module('openshiftConsole') |
71 | 71 |
var fragmentParams = new URI("?" + u.fragment()).query(true); |
72 | 72 |
authLogger.log("RedirectLoginService.finish()", queryParams, fragmentParams); |
73 | 73 |
|
74 |
- // Error codes can come in query params or fragment params |
|
75 |
- // Handle an error response from the OAuth server |
|
74 |
+ // Error codes can come in query params or fragment params |
|
75 |
+ // Handle an error response from the OAuth server |
|
76 | 76 |
var error = queryParams.error || fragmentParams.error; |
77 |
- if (error) { |
|
78 |
- var error_description = queryParams.error_description || fragmentParams.error_description; |
|
79 |
- var error_uri = queryParams.error_uri || fragmentParams.error_uri; |
|
77 |
+ if (error) { |
|
78 |
+ var error_description = queryParams.error_description || fragmentParams.error_description; |
|
79 |
+ var error_uri = queryParams.error_uri || fragmentParams.error_uri; |
|
80 | 80 |
authLogger.log("RedirectLoginService.finish(), error", error, error_description, error_uri); |
81 | 81 |
return $q.reject({ |
82 | 82 |
error: error, |
... | ... |
@@ -85,13 +149,16 @@ angular.module('openshiftConsole') |
85 | 85 |
}); |
86 | 86 |
} |
87 | 87 |
|
88 |
+ var stateData = parseState(fragmentParams.state); |
|
89 |
+ |
|
88 | 90 |
// Handle an access_token response |
89 | 91 |
if (fragmentParams.access_token && (fragmentParams.token_type || "").toLowerCase() === "bearer") { |
90 | 92 |
var deferred = $q.defer(); |
91 | 93 |
deferred.resolve({ |
92 | 94 |
token: fragmentParams.access_token, |
93 | 95 |
ttl: fragmentParams.expires_in, |
94 |
- then: fragmentParams.state |
|
96 |
+ then: stateData.state, |
|
97 |
+ verified: stateData.verified |
|
95 | 98 |
}); |
96 | 99 |
return deferred.promise; |
97 | 100 |
} |
... | ... |
@@ -2,12 +2,26 @@ |
2 | 2 |
<div class="wrap no-sidebar"> |
3 | 3 |
<div class="middle surface-shaded"> |
4 | 4 |
<div class="container surface-shaded"> |
5 |
- <div> |
|
5 |
+ <div ng-if="!confirmUser"> |
|
6 | 6 |
<h1 style="margin-top: 10px;">Logging in…</h1> |
7 |
- <div> |
|
8 |
- Please wait while you are logged in... |
|
9 |
- </div> |
|
7 |
+ <p>Please wait while you are logged in…</p> |
|
10 | 8 |
</div> |
9 |
+ |
|
10 |
+ <div ng-if="confirmUser && !overriddenUser"> |
|
11 |
+ <h1 style="margin-top: 10px;">Confirm Login</h1> |
|
12 |
+ <p>You are being logged in as <code>{{confirmUser.metadata.name}}</code>.</p> |
|
13 |
+ <button class="btn btn-lg btn-primary" type="button" ng-click="completeLogin();">Continue</button> |
|
14 |
+ <button class="btn btn-lg btn-default" type="button" ng-click="cancelLogin();">Cancel</button> |
|
15 |
+ </div> |
|
16 |
+ |
|
17 |
+ <div ng-if="confirmUser && overriddenUser"> |
|
18 |
+ <h1 style="margin-top: 10px;">Confirm User Change</h1> |
|
19 |
+ <p>You are about to change users from <code>{{overriddenUser.metadata.name}}</code> to <code>{{confirmUser.metadata.name}}</code>.</p> |
|
20 |
+ <p>If this is unexpected, click Cancel. This could be an attempt to trick you into acting as another user.</p> |
|
21 |
+ <button class="btn btn-lg btn-danger" type="button" ng-click="completeLogin();">Switch Users</button> |
|
22 |
+ <button class="btn btn-lg btn-primary" type="button" ng-click="cancelLogin();">Cancel</button> |
|
23 |
+ </div> |
|
24 |
+ |
|
11 | 25 |
</div> |
12 | 26 |
</div> |
13 | 27 |
</div> |
... | ... |
@@ -2582,7 +2582,49 @@ return a && (b = a), b; |
2582 | 2582 |
}, this.OAuthRedirectURI = function(a) { |
2583 | 2583 |
return a && (c = a), c; |
2584 | 2584 |
}, this.$get = [ "$location", "$q", "Logger", function(d, e, f) { |
2585 |
-var g = f.get("auth"); |
|
2585 |
+var g = f.get("auth"), h = function(a) { |
|
2586 |
+var b; |
|
2587 |
+if (window.crypto && window.Uint32Array) try { |
|
2588 |
+var c = new Uint32Array(a); |
|
2589 |
+window.crypto.getRandomValues(c), b = []; |
|
2590 |
+for (var d = 0; a > d; d++) b.push(c[d]); |
|
2591 |
+} catch (e) { |
|
2592 |
+g.debug("RedirectLoginService.getRandomInts: ", e), b = null; |
|
2593 |
+} |
|
2594 |
+if (!b) { |
|
2595 |
+b = []; |
|
2596 |
+for (var f = 0; a > f; f++) b.push(Math.floor(4294967296 * Math.random())); |
|
2597 |
+} |
|
2598 |
+return b; |
|
2599 |
+}, i = "RedirectLoginService.nonce", j = function(a) { |
|
2600 |
+var b = String(new Date().getTime()) + "-" + h(8).join(""); |
|
2601 |
+try { |
|
2602 |
+window.localStorage[i] = b; |
|
2603 |
+} catch (c) { |
|
2604 |
+g.log("RedirectLoginService.makeState, localStorage error: ", c); |
|
2605 |
+} |
|
2606 |
+return JSON.stringify({ |
|
2607 |
+then:a, |
|
2608 |
+nonce:b |
|
2609 |
+}); |
|
2610 |
+}, k = function(a) { |
|
2611 |
+var b = { |
|
2612 |
+then:null, |
|
2613 |
+verified:!1 |
|
2614 |
+}, c = ""; |
|
2615 |
+try { |
|
2616 |
+c = window.localStorage[i], window.localStorage.removeItem(i); |
|
2617 |
+} catch (d) { |
|
2618 |
+g.log("RedirectLoginService.parseState, localStorage error: ", d); |
|
2619 |
+} |
|
2620 |
+try { |
|
2621 |
+var e = a ? JSON.parse(a) :{}; |
|
2622 |
+e && e.nonce && c && e.nonce === c && (b.verified = !0, b.then = e.then); |
|
2623 |
+} catch (d) { |
|
2624 |
+g.error("RedirectLoginService.parseState, state error: ", d); |
|
2625 |
+} |
|
2626 |
+return g.error("RedirectLoginService.parseState", b), b; |
|
2627 |
+}; |
|
2586 | 2628 |
return { |
2587 | 2629 |
login:function() { |
2588 | 2630 |
if ("" === a) return e.reject({ |
... | ... |
@@ -2601,7 +2643,7 @@ var f = e.defer(), h = new URI(b), i = new URI(d.url()).fragment(""); |
2601 | 2601 |
return h.query({ |
2602 | 2602 |
client_id:a, |
2603 | 2603 |
response_type:"token", |
2604 |
-state:i.toString(), |
|
2604 |
+state:j(i.toString()), |
|
2605 | 2605 |
redirect_uri:c |
2606 | 2606 |
}), g.log("RedirectLoginService.login(), redirecting", h.toString()), window.location.href = h.toString(), f.promise; |
2607 | 2607 |
}, |
... | ... |
@@ -2617,13 +2659,15 @@ error_description:h, |
2617 | 2617 |
error_uri:i |
2618 | 2618 |
}); |
2619 | 2619 |
} |
2620 |
+var j = k(c.state); |
|
2620 | 2621 |
if (c.access_token && "bearer" === (c.token_type || "").toLowerCase()) { |
2621 |
-var j = e.defer(); |
|
2622 |
-return j.resolve({ |
|
2622 |
+var l = e.defer(); |
|
2623 |
+return l.resolve({ |
|
2623 | 2624 |
token:c.access_token, |
2624 | 2625 |
ttl:c.expires_in, |
2625 |
-then:c.state |
|
2626 |
-}), j.promise; |
|
2626 |
+then:j.state, |
|
2627 |
+verified:j.verified |
|
2628 |
+}), l.promise; |
|
2627 | 2629 |
} |
2628 | 2630 |
return e.reject({ |
2629 | 2631 |
error:"invalid_request", |
... | ... |
@@ -4924,35 +4968,43 @@ hideFilterWidget:!0 |
4924 | 4924 |
}, c.get(a.project).then(_.spread(function(a, c) { |
4925 | 4925 |
b.project = a, b.projectContext = c; |
4926 | 4926 |
})); |
4927 |
-} ]), angular.module("openshiftConsole").controller("OAuthController", [ "$location", "$q", "RedirectLoginService", "DataService", "AuthService", "Logger", function(a, b, c, d, e, f) { |
|
4928 |
-var g = f.get("auth"); |
|
4929 |
-c.finish().then(function(b) { |
|
4930 |
-var c = b.token, f = b.then, h = b.ttl, i = { |
|
4927 |
+} ]), angular.module("openshiftConsole").controller("OAuthController", [ "$scope", "$location", "$q", "RedirectLoginService", "DataService", "AuthService", "Logger", function(a, b, c, d, e, f, g) { |
|
4928 |
+var h = g.get("auth"); |
|
4929 |
+a.completeLogin = function() {}, a.cancelLogin = function() { |
|
4930 |
+b.replace(), b.url("./"); |
|
4931 |
+}, d.finish().then(function(c) { |
|
4932 |
+var d = c.token, g = c.then, i = c.verified, j = c.ttl, k = { |
|
4931 | 4933 |
errorNotification:!1, |
4932 | 4934 |
http:{ |
4933 | 4935 |
auth:{ |
4934 |
-token:c, |
|
4936 |
+token:d, |
|
4935 | 4937 |
triggerLogin:!1 |
4936 | 4938 |
} |
4937 | 4939 |
} |
4938 | 4940 |
}; |
4939 |
-g.log("OAuthController, got token, fetching user", i), d.get("users", "~", {}, i).then(function(b) { |
|
4940 |
-g.log("OAuthController, got user", b), e.setUser(b, c, h); |
|
4941 |
-var d = f || "./"; |
|
4942 |
-URI(d).is("absolute") && (g.log("OAuthController, invalid absolute redirect", d), d = "./"), g.log("OAuthController, redirecting", d), a.url(d); |
|
4943 |
-})["catch"](function(b) { |
|
4941 |
+h.log("OAuthController, got token, fetching user", k), e.get("users", "~", {}, k).then(function(c) { |
|
4942 |
+if (h.log("OAuthController, got user", c), a.completeLogin = function() { |
|
4943 |
+f.setUser(c, d, j); |
|
4944 |
+var a = g || "./"; |
|
4945 |
+URI(a).is("absolute") && (h.log("OAuthController, invalid absolute redirect", a), a = "./"), h.log("OAuthController, redirecting", a), b.replace(), b.url(a); |
|
4946 |
+}, i) a.completeLogin(); else { |
|
4947 |
+a.confirmUser = c; |
|
4948 |
+var e = f.UserStore().getUser(); |
|
4949 |
+e && e.metadata.name !== c.metadata.name && (a.overriddenUser = e); |
|
4950 |
+} |
|
4951 |
+})["catch"](function(a) { |
|
4944 | 4952 |
var c = URI("error").query({ |
4945 | 4953 |
error:"user_fetch_failed" |
4946 | 4954 |
}).toString(); |
4947 |
-g.error("OAuthController, error fetching user", b, "redirecting", c), a.url(c); |
|
4955 |
+h.error("OAuthController, error fetching user", a, "redirecting", c), b.replace(), b.url(c); |
|
4948 | 4956 |
}); |
4949 |
-})["catch"](function(b) { |
|
4957 |
+})["catch"](function(a) { |
|
4950 | 4958 |
var c = URI("error").query({ |
4951 |
-error:b.error || "", |
|
4952 |
-error_description:b.error_description || "", |
|
4953 |
-error_uri:b.error_uri || "" |
|
4959 |
+error:a.error || "", |
|
4960 |
+error_description:a.error_description || "", |
|
4961 |
+error_uri:a.error_uri || "" |
|
4954 | 4962 |
}).toString(); |
4955 |
-g.error("OAuthController, error", b, "redirecting", c), a.url(c); |
|
4963 |
+h.error("OAuthController, error", a, "redirecting", c), b.replace(), b.url(c); |
|
4956 | 4964 |
}); |
4957 | 4965 |
} ]), angular.module("openshiftConsole").controller("ErrorController", [ "$scope", function(a) { |
4958 | 4966 |
var b = URI(window.location.href).query(!0), c = b.error; |
... | ... |
@@ -14184,11 +14236,22 @@ var _scriptsTemplatesJs = []byte(`angular.module('openshiftConsoleTemplates', [] |
14184 | 14184 |
"<div class=\"wrap no-sidebar\">\n" + |
14185 | 14185 |
"<div class=\"middle surface-shaded\">\n" + |
14186 | 14186 |
"<div class=\"container surface-shaded\">\n" + |
14187 |
- "<div>\n" + |
|
14187 |
+ "<div ng-if=\"!confirmUser\">\n" + |
|
14188 | 14188 |
"<h1 style=\"margin-top: 10px\">Logging in…</h1>\n" + |
14189 |
- "<div>\n" + |
|
14190 |
- "Please wait while you are logged in...\n" + |
|
14191 |
- "</div>\n" + |
|
14189 |
+ "<p>Please wait while you are logged in…</p>\n" + |
|
14190 |
+ "</div>\n" + |
|
14191 |
+ "<div ng-if=\"confirmUser && !overriddenUser\">\n" + |
|
14192 |
+ "<h1 style=\"margin-top: 10px\">Confirm Login</h1>\n" + |
|
14193 |
+ "<p>You are being logged in as <code>{{confirmUser.metadata.name}}</code>.</p>\n" + |
|
14194 |
+ "<button class=\"btn btn-lg btn-primary\" type=\"button\" ng-click=\"completeLogin();\">Continue</button>\n" + |
|
14195 |
+ "<button class=\"btn btn-lg btn-default\" type=\"button\" ng-click=\"cancelLogin();\">Cancel</button>\n" + |
|
14196 |
+ "</div>\n" + |
|
14197 |
+ "<div ng-if=\"confirmUser && overriddenUser\">\n" + |
|
14198 |
+ "<h1 style=\"margin-top: 10px\">Confirm User Change</h1>\n" + |
|
14199 |
+ "<p>You are about to change users from <code>{{overriddenUser.metadata.name}}</code> to <code>{{confirmUser.metadata.name}}</code>.</p>\n" + |
|
14200 |
+ "<p>If this is unexpected, click Cancel. This could be an attempt to trick you into acting as another user.</p>\n" + |
|
14201 |
+ "<button class=\"btn btn-lg btn-danger\" type=\"button\" ng-click=\"completeLogin();\">Switch Users</button>\n" + |
|
14202 |
+ "<button class=\"btn btn-lg btn-primary\" type=\"button\" ng-click=\"cancelLogin();\">Cancel</button>\n" + |
|
14192 | 14203 |
"</div>\n" + |
14193 | 14204 |
"</div>\n" + |
14194 | 14205 |
"</div>\n" + |