Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSRF updates - 1.2.0 #8958

Merged
merged 3 commits into from
May 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions assets/app/scripts/controllers/util/oauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@
* Controller of the openshiftConsole
*/
angular.module('openshiftConsole')
.controller('OAuthController', function ($location, $q, RedirectLoginService, DataService, AuthService, Logger) {
.controller('OAuthController', function ($scope, $location, $q, RedirectLoginService, DataService, AuthService, Logger) {
var authLogger = Logger.get("auth");

// Initialize to a no-op function.
// Needed to let the view confirm a login when the state is unverified.
$scope.completeLogin = function(){};
$scope.cancelLogin = function() {
$location.replace();
$location.url("./");
};

RedirectLoginService.finish()
.then(function(data) {
var token = data.token;
var then = data.then;
var verified = data.verified;
var ttl = data.ttl;

// Try to fetch the user
Expand All @@ -25,21 +34,41 @@ angular.module('openshiftConsole')
.then(function(user) {
// Set the new user and token in the auth service
authLogger.log("OAuthController, got user", user);
AuthService.setUser(user, token, ttl);

// Redirect to original destination (or default to '/')
var destination = then || './';
if (URI(destination).is('absolute')) {
authLogger.log("OAuthController, invalid absolute redirect", destination);
destination = './';
$scope.completeLogin = function() {
// Persist the user
AuthService.setUser(user, token, ttl);

// Redirect to original destination (or default to './')
var destination = then || './';
if (URI(destination).is('absolute')) {
authLogger.log("OAuthController, invalid absolute redirect", destination);
destination = './';
}
authLogger.log("OAuthController, redirecting", destination);
$location.replace();
$location.url(destination);
};

if (verified) {
// Automatically complete
$scope.completeLogin();
} else {
// Require the UI to prompt
$scope.confirmUser = user;

// Additionally, give the UI info about the user being overridden
var currentUser = AuthService.UserStore().getUser();
if (currentUser && currentUser.metadata.name !== user.metadata.name) {
$scope.overriddenUser = currentUser;
}
}
authLogger.log("OAuthController, redirecting", destination);
$location.url(destination);
})
.catch(function(rejection) {
// Handle an API error response fetching the user
var redirect = URI('error').query({error: 'user_fetch_failed'}).toString();
authLogger.error("OAuthController, error fetching user", rejection, "redirecting", redirect);
$location.replace();
$location.url(redirect);
});

Expand All @@ -51,6 +80,7 @@ angular.module('openshiftConsole')
error_uri: rejection.error_uri || ""
}).toString();
authLogger.error("OAuthController, error", rejection, "redirecting", redirect);
$location.replace();
$location.url(redirect);
});

Expand Down
83 changes: 75 additions & 8 deletions assets/app/scripts/services/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,70 @@ angular.module('openshiftConsole')
this.$get = function($location, $q, Logger) {
var authLogger = Logger.get("auth");

var getRandomInts = function(length) {
var randomValues;

if (window.crypto && window.Uint32Array) {
try {
var r = new Uint32Array(length);
window.crypto.getRandomValues(r);
randomValues = [];
for (var j=0; j < length; j++) {
randomValues.push(r[j]);
}
} catch(e) {
authLogger.debug("RedirectLoginService.getRandomInts: ", e);
randomValues = null;
}
}

if (!randomValues) {
randomValues = [];
for (var i=0; i < length; i++) {
randomValues.push(Math.floor(Math.random() * 4294967296));
}
}

return randomValues;
};

var nonceKey = "RedirectLoginService.nonce";
var makeState = function(then) {
var nonce = String(new Date().getTime()) + "-" + getRandomInts(8).join("");
try {
window.localStorage[nonceKey] = nonce;
} catch(e) {
authLogger.log("RedirectLoginService.makeState, localStorage error: ", e);
}
return JSON.stringify({then: then, nonce:nonce});
};
var parseState = function(state) {
var retval = {
then: null,
verified: false
};

var nonce = "";
try {
nonce = window.localStorage[nonceKey];
window.localStorage.removeItem(nonceKey);
} catch(e) {
authLogger.log("RedirectLoginService.parseState, localStorage error: ", e);
}

try {
var data = state ? JSON.parse(state) : {};
if (data && data.nonce && nonce && data.nonce === nonce) {
retval.verified = true;
retval.then = data.then;
}
} catch(e) {
authLogger.error("RedirectLoginService.parseState, state error: ", e);
}
authLogger.error("RedirectLoginService.parseState", retval);
return retval;
};

return {
// Returns a promise that resolves with {user:{...}, token:'...', ttl:X}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
login: function() {
Expand All @@ -49,7 +113,7 @@ angular.module('openshiftConsole')
uri.query({
client_id: _oauth_client_id,
response_type: 'token',
state: returnUri.toString(),
state: makeState(returnUri.toString()),
redirect_uri: _oauth_redirect_uri
});
authLogger.log("RedirectLoginService.login(), redirecting", uri.toString());
Expand All @@ -59,7 +123,7 @@ angular.module('openshiftConsole')
},

// Parses oauth callback parameters from window.location
// Returns a promise that resolves with {token:'...',then:'...'}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
// Returns a promise that resolves with {token:'...',then:'...',verified:true|false}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
// If no token and no error is present, resolves with {}
// Example error codes: https://tools.ietf.org/html/rfc6749#section-5.2
finish: function() {
Expand All @@ -71,12 +135,12 @@ angular.module('openshiftConsole')
var fragmentParams = new URI("?" + u.fragment()).query(true);
authLogger.log("RedirectLoginService.finish()", queryParams, fragmentParams);

// Error codes can come in query params or fragment params
// Handle an error response from the OAuth server
// Error codes can come in query params or fragment params
// Handle an error response from the OAuth server
var error = queryParams.error || fragmentParams.error;
if (error) {
var error_description = queryParams.error_description || fragmentParams.error_description;
var error_uri = queryParams.error_uri || fragmentParams.error_uri;
if (error) {
var error_description = queryParams.error_description || fragmentParams.error_description;
var error_uri = queryParams.error_uri || fragmentParams.error_uri;
authLogger.log("RedirectLoginService.finish(), error", error, error_description, error_uri);
return $q.reject({
error: error,
Expand All @@ -85,13 +149,16 @@ angular.module('openshiftConsole')
});
}

var stateData = parseState(fragmentParams.state);

// Handle an access_token response
if (fragmentParams.access_token && (fragmentParams.token_type || "").toLowerCase() === "bearer") {
var deferred = $q.defer();
deferred.resolve({
token: fragmentParams.access_token,
ttl: fragmentParams.expires_in,
then: fragmentParams.state
then: stateData.state,
verified: stateData.verified
});
return deferred.promise;
}
Expand Down
22 changes: 18 additions & 4 deletions assets/app/views/util/oauth.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
<div class="wrap no-sidebar">
<div class="middle surface-shaded">
<div class="container surface-shaded">
<div>
<div ng-if="!confirmUser">
<h1 style="margin-top: 10px;">Logging in&hellip;</h1>
<div>
Please wait while you are logged in...
</div>
<p>Please wait while you are logged in&hellip;</p>
</div>

<div ng-if="confirmUser && !overriddenUser">
<h1 style="margin-top: 10px;">Confirm Login</h1>
<p>You are being logged in as <code>{{confirmUser.metadata.name}}</code>.</p>
<button class="btn btn-lg btn-primary" type="button" ng-click="completeLogin();">Continue</button>
<button class="btn btn-lg btn-default" type="button" ng-click="cancelLogin();">Cancel</button>
</div>

<div ng-if="confirmUser && overriddenUser">
<h1 style="margin-top: 10px;">Confirm User Change</h1>
<p>You are about to change users from <code>{{overriddenUser.metadata.name}}</code> to <code>{{confirmUser.metadata.name}}</code>.</p>
<p>If this is unexpected, click Cancel. This could be an attempt to trick you into acting as another user.</p>
<button class="btn btn-lg btn-danger" type="button" ng-click="completeLogin();">Switch Users</button>
<button class="btn btn-lg btn-primary" type="button" ng-click="cancelLogin();">Cancel</button>
</div>

</div>
</div>
</div>
Loading