348 lines
11 KiB
JavaScript
348 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @ngdoc function
|
|
* @name yololiumApp.controller:OauthCtrl
|
|
* @description
|
|
* # OauthCtrl
|
|
* Controller of the yololiumApp
|
|
*/
|
|
angular.module('yololiumApp')
|
|
.controller('AuthorizationDialogController', [
|
|
'$window'
|
|
, '$location'
|
|
, '$stateParams'
|
|
, '$q'
|
|
, '$timeout'
|
|
, '$scope'
|
|
, '$http'
|
|
, 'DaplieApiConfig'
|
|
, 'DaplieApiSession'
|
|
, 'DaplieApiRequest'
|
|
, function (
|
|
$window
|
|
, $location
|
|
, $stateParams
|
|
, $q
|
|
, $timeout
|
|
, $scope
|
|
, $http
|
|
, LdsApiConfig
|
|
, LdsApiSession
|
|
, LdsApiRequest
|
|
) {
|
|
|
|
var scope = this;
|
|
|
|
function isIframe () {
|
|
try {
|
|
return window.self !== window.top;
|
|
} catch (e) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// TODO move into config
|
|
var scopeMessages = {
|
|
directories: "View directories"
|
|
, me: "View your own Account"
|
|
, '*': "Use the Full Developer API"
|
|
};
|
|
|
|
function updateAccepted() {
|
|
scope.acceptedString = scope.pendingScope.filter(function (obj) {
|
|
return obj.acceptable && obj.accepted;
|
|
}).map(function (obj) {
|
|
return obj.value;
|
|
}).join(' ');
|
|
|
|
return scope.acceptedString;
|
|
}
|
|
|
|
function scopeStrToObj(value, accepted) {
|
|
// TODO parse subresource (dns:example.com:cname)
|
|
return {
|
|
accepted: accepted
|
|
, acceptable: !!scopeMessages[value]
|
|
, name: scopeMessages[value] || 'Invalid Scope \'' + value + '\''
|
|
, value: value
|
|
};
|
|
}
|
|
|
|
function requestSelectedAccount(account, query, origin) {
|
|
// TODO Desired Process
|
|
// * check locally
|
|
// * if permissions pass, sign a jwt and post to server
|
|
// * if permissions fail, get from server (posting public key), then sign jwt
|
|
// * redirect to authorization_code_callback?code= or oauth3.html#token=
|
|
return $http.get(
|
|
LdsApiConfig.providerUri + '/api/org.oauth3.accounts/:account_id/grants/:client_id'
|
|
.replace(/:account_id/g, account.accountId)
|
|
.replace(/:client_id/g, query.client_id)
|
|
, { headers: { Authorization: "Bearer " + account.token } }
|
|
).then(function (resp) {
|
|
var err;
|
|
|
|
if (!resp.data) {
|
|
err = new Error("[Uknown Error] got no response (not even an error)");
|
|
console.error(err.stack);
|
|
throw err;
|
|
}
|
|
|
|
if (resp.data.error) {
|
|
console.error('[authorization-dialog] resp.data');
|
|
err = new Error(resp.data.error.message || resp.data.error_description);
|
|
console.error(err.stack);
|
|
scope.error = resp.data.error;
|
|
scope.rawResponse = resp.data;
|
|
return $q.reject(err);
|
|
}
|
|
|
|
return resp.data;
|
|
});
|
|
}
|
|
|
|
scope.chooseAccount = function (/*profile*/) {
|
|
$window.alert("user switching not yet implemented");
|
|
};
|
|
scope.updateScope = function () {
|
|
updateAccepted();
|
|
};
|
|
|
|
function parseScope(scope) {
|
|
return (scope||'').split(/[\s,]/g)
|
|
}
|
|
function getNewPermissions(grant, query) {
|
|
var grantedArr = parseScope(grant.scope);
|
|
var requestedArr = parseScope(query.scope||'');
|
|
|
|
return requestedArr.filter(function (scope) {
|
|
return -1 === grantedArr.indexOf(scope);
|
|
});
|
|
}
|
|
|
|
function generateToken(account, grant, query) {
|
|
var err = new Error("generateToken not yet implemented");
|
|
throw err;
|
|
}
|
|
|
|
function generateCode(account, grant, query) {
|
|
var err = new Error("generateCode not yet implemented");
|
|
throw err;
|
|
}
|
|
|
|
function getAccountPermissions(account, query, origin) {
|
|
return requestSelectedAccount(account, query, origin).then(function (grants) {
|
|
var grant = grants[query.client_id] || grants;
|
|
var grantedArr = parseScope(grant.scope);
|
|
var pendingArr = getNewPermissions(grant, query);
|
|
|
|
var grantedObj = grantedArr.map(scopeStrToObj);
|
|
// '!' is a debug scope that ensures the permission dialog will be activated
|
|
// also could be used for switch user
|
|
var pendingObj = pendingArr.filter(function (v) { return '!' !== v; }).map(scopeStrToObj);
|
|
|
|
scope.client = grant.client;
|
|
|
|
if (!scope.client.title) {
|
|
scope.client.title = scope.client.name || 'Missing App Title';
|
|
}
|
|
|
|
scope.selectedAccountId = account.accountId;
|
|
|
|
if (!checkRedirect(grant, query)) {
|
|
location.href = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK';
|
|
return;
|
|
}
|
|
|
|
// key generation in browser
|
|
// possible iframe vulns?
|
|
if (pendingArr.length) {
|
|
if (scope.iframe) {
|
|
location.href = query.redirect_uri + '#error=access_denied&error_description='
|
|
+ encodeURIComponent("You're requesting permission in an iframe, but the permissions have not yet been granted")
|
|
+ '&error_uri=' + encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED');
|
|
return;
|
|
}
|
|
|
|
updateAccepted();
|
|
return grant;
|
|
}
|
|
else if ('token' === query.response_type) {
|
|
generateToken(account, grant, query).then(function (token) {
|
|
location.href = query.redirect_uri + '#token=' + token;
|
|
});
|
|
return;
|
|
}
|
|
else if ('code' === query.response_type) {
|
|
// NOTE
|
|
// A client secret may never be exposed in a client
|
|
// A code always requires a secret
|
|
// Therefore this redirect_uri will always be to a server, not a local page
|
|
generateCode(account, grant, query).then(function () {
|
|
location.href = query.redirect_uri + '?code=' + code;
|
|
});
|
|
return;
|
|
} else {
|
|
location.href = query.redirect_uri + '#error=E_UNKNOWN_RESPONSE_TYPE&error_description='
|
|
+ encodeURIComponent("The '?response_type=' parameter must be set to either 'token' or 'code'.")
|
|
+ '&error_uri=' + encodeURIComponent('https://oauth3.org/docs/errors/#E_UNKNOWN_RESPONSE_TYPE');
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
function redirectToFailure() {
|
|
var redirectUri = $location.search().redirect_uri;
|
|
|
|
var parser = document.createElement('a');
|
|
parser.href = redirectUri;
|
|
if (parser.search) {
|
|
parser.search += '&';
|
|
} else {
|
|
parser.search += '?';
|
|
}
|
|
parser.search += 'error=E_NO_SESSION';
|
|
redirectUri = parser.href;
|
|
|
|
window.location.href = redirectUri;
|
|
}
|
|
|
|
function initAccount(session, query, origin) {
|
|
return LdsApiRequest.getAccountSummaries(session).then(function (accounts) {
|
|
var account = LdsApiSession.selectAccount(session);
|
|
var profile;
|
|
|
|
scope.accounts = accounts.map(function (account) {
|
|
return account.profile.me;
|
|
});
|
|
accounts.some(function (a) {
|
|
if (LdsApiSession.getId(a) === LdsApiSession.getId(account)) {
|
|
profile = a.profile;
|
|
a.selected = true;
|
|
return true;
|
|
}
|
|
});
|
|
|
|
if (profile.me.photos[0]) {
|
|
if (!profile.me.photos[0].appScopedId) {
|
|
// TODO fix API to ensure corrent id
|
|
profile.me.photos[0].appScopedId = profile.me.appScopedId || profile.me.app_scoped_id;
|
|
}
|
|
}
|
|
profile.me.photo = profile.me.photos[0] && LdsApiRequest.photoUrl(account, profile.me.photos[0], 'medium');
|
|
scope.account = profile.me;
|
|
|
|
scope.token = $stateParams.token;
|
|
|
|
/*
|
|
scope.accounts.push({
|
|
displayName: 'Login as a different user'
|
|
, new: true
|
|
});
|
|
*/
|
|
|
|
//return determinePermissions(session, account);
|
|
return getAccountPermissions(account, query, origin).then(function () {
|
|
// do nothing?
|
|
scope.selectedAccount = session; //.account;
|
|
scope.previousAccount = session; //.account;
|
|
scope.updateScope();
|
|
}, function (err) {
|
|
if (/logged in/.test(err.message)) {
|
|
return LdsApiSession.destroy().then(function () {
|
|
init();
|
|
});
|
|
}
|
|
|
|
if ('E_INVALID_TRANSACTION' === err.code) {
|
|
window.alert(err.message);
|
|
return;
|
|
}
|
|
|
|
console.warn("[ldsconnect.org] [authorization-dialog] ERROR somewhere in oauth process");
|
|
console.warn(err);
|
|
window.alert(err.message);
|
|
});
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
scope.iframe = isIframe();
|
|
var query = $location.search();
|
|
var referrer = $window.document.referer || $window.document.origin;
|
|
// TODO XXX this should be drawn from site-specific config
|
|
var apiHost = 'https://oauth3.org';
|
|
|
|
// if the client didn't specify an id the client is the referrer
|
|
if (!query.client_id) {
|
|
// if we were redirect here by our own apiHost we can trust the host as the client_id
|
|
// (and it will be checked against allowed urls anyway)
|
|
if (referrer === apiHost) {
|
|
query.client_id = ('https://' + query.host);
|
|
} else {
|
|
query.client_id = referrer;
|
|
}
|
|
}
|
|
|
|
// TODO XXX to allow or to disallow mounted apps, that is the question
|
|
// https://example.com/blah/ -> example.com/blah
|
|
query.client_id = query.client_id.replace(/^https?:\/\//i, '').replace(/\/$/, '');
|
|
|
|
if (scope.iframe) {
|
|
return LdsApiSession.checkSession().then(function (session) {
|
|
if (session.accounts.length) {
|
|
// TODO make sure this fails / notifies
|
|
return initAccount(session, query, origin);
|
|
} else {
|
|
// TODO also notify to bring to front
|
|
redirectToFailure();
|
|
}
|
|
});
|
|
}
|
|
|
|
// session means both login(s) and account(s)
|
|
return LdsApiSession.requireSession(
|
|
// role
|
|
null
|
|
// TODO login opts (these are hypothetical)
|
|
, { close: false
|
|
, options: ['login', 'create']
|
|
, default: 'login'
|
|
}
|
|
// TODO account opts
|
|
, { verify: ['email', 'phone']
|
|
}
|
|
, { clientId: query.clientId
|
|
}
|
|
).then(function (session) {
|
|
initAccount(session, query, origin)
|
|
});
|
|
}
|
|
|
|
init();
|
|
|
|
// I couldn't figure out how to get angular to bubble the event
|
|
// and the oauth2orize framework didn't seem to work with json form uploads
|
|
// so I dropped down to quick'n'dirty jQuery to get it all to work
|
|
scope.hackFormSubmit = function (opts) {
|
|
scope.submitting = true;
|
|
scope.cancelHack = !opts.allow;
|
|
scope.authorizationDecisionUri = LdsApiConfig.providerUri + '/api/oauth3/authorization_decision';
|
|
scope.updateScope();
|
|
|
|
$window.jQuery('form.js-hack-hidden-form').attr('action', scope.authorizationDecisionUri);
|
|
|
|
// give time for the apply to take place
|
|
$timeout(function () {
|
|
$window.jQuery('form.js-hack-hidden-form').submit();
|
|
}, 50);
|
|
};
|
|
scope.allowHack = function () {
|
|
scope.hackFormSubmit({ allow: true });
|
|
};
|
|
scope.rejectHack = function () {
|
|
scope.hackFormSubmit({ allow: false });
|
|
};
|
|
}]);
|