From 203bd24368c33d570005b9ec00328fb70746f938 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 10 Feb 2017 21:34:00 -0500 Subject: [PATCH] Freaking Works! --- oauth3.browser.js | 5 +- oauth3.core.js | 3 ++ oauth3.core.provider.js | 108 +++++++++++++++++++++++++++++++++++++++- oauth3.js | 69 +++++++++++++++++++++++-- oauth3.provider.js | 94 ++++++++++++++++++++++++++++++++++ 5 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 oauth3.provider.js diff --git a/oauth3.browser.js b/oauth3.browser.js index 0704115..7cc00f1 100644 --- a/oauth3.browser.js +++ b/oauth3.browser.js @@ -13,7 +13,10 @@ } var browser = exports.OAUTH3_BROWSER = { - discover: function (providerUri, opts) { + clientUri: function (location) { + return OAUTH3_CORE.normalizeUri(location.host + location.pathname); + } + , discover: function (providerUri, opts) { if (!providerUri) { throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri); } diff --git a/oauth3.core.js b/oauth3.core.js index 0eedf01..9845a21 100644 --- a/oauth3.core.js +++ b/oauth3.core.js @@ -11,6 +11,9 @@ return 'https://' + window.location.host; } + core.parsescope = function (scope) { + return (scope||'').split(/[+, ]/g); + }; core.stringifyscope = function (scope) { if (Array.isArray(scope)) { scope = scope.join(' '); diff --git a/oauth3.core.provider.js b/oauth3.core.provider.js index 7cc3be9..1b0b405 100644 --- a/oauth3.core.provider.js +++ b/oauth3.core.provider.js @@ -147,8 +147,13 @@ if (!opts.method) { console.warn("You must supply options.method as either 'GET', or 'POST'"); } - if ('POST' === opts.method && !opts.scope) { - console.warn("You must supply options.scope as a space-delimited string of scopes"); + if ('POST' === opts.method) { + if ('string' !== typeof opts.scope) { + console.warn("You should supply options.scope as a space-delimited string of scopes"); + } + if (-1 === ['token', 'code'].indexOf(opts.response_type)) { + throw new Error("You must supply options.response_type as 'token' or 'code'"); + } } var url = core.urls.resolve(directive.issuer, directive.grants.url) @@ -187,6 +192,105 @@ console.info(url); throw new Error("NOT IMPLEMENTED authorization_decision"); }; + core.authz = core.authz || {}; + core.authz.scopes = function (session, clientParams) { + // OAuth3.requests.grants(providerUri, {}); // return list of grants + // OAuth3.checkGrants(providerUri, {}); // + var clientUri = OAUTH3.core.normalizeUri(clientParams.client_uri || window.document.referrer); + var scope = clientParams.scope || ''; + var clientObj = clientParams; + + if (!scope) { + scope = 'oauth3_authn'; + } + + //$('.js-user-avatar').attr('src', userAvatar); + + console.log('grants options'); + console.log(loc.hash); + console.log(loc.search); + console.log(clientObj); + console.log(session.meta); + console.log(window.document.referrer); + + return OAUTH3.requests.grants(CONFIG.host, { + method: 'GET' + , client_id: clientUri + , client_uri: clientUri + , session: session + }).then(function (grantResults) { + var grants; + var grantedScopes; + var grantedScopesMap; + var pendingScopes; + var acceptedScopes; + var scopes = scope.split(/[+, ]/g); + var callbackUrl; + + console.log('previous grants:'); + console.log(grantResults); + + if (grantResults.data.error) { + window.alert('grantResults: ' + grantResults.data.errorDescription || grantResults.data.error.message); + return; + } + + // it doesn't matter who the referrer is as long as the destination + // is an authorized destination for the client in question + // (though it may not hurt to pass the referrer's info on to the client) + if (!OAUTH3.checkRedirect(grantResults.data.client, clientObj)) { + callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK' + + '?redirect_uri=' + clientObj.redirect_uri + + '&allowed_urls=' + grantResults.data.client.url + + '&client_id=' + clientUri + + '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer) + ; + location.href = callbackUrl; + return; + } + + if ('oauth3_authn' === scope) { + // implicit ppid grant is automatic + console.warn('[security] fix scope checking on backend so that we can do automatic grants'); + // TODO check user preference if implicit ppid grant is allowed + //return generateToken(session, clientObj); + } + + grants = grantResults.originalData.grants.filter(function (grant) { + if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) { + return true; + } + }); + + grantedScopesMap = {}; + acceptedScopes = []; + pendingScopes = scopes.filter(function (requestedScope) { + return grants.every(function (grant) { + if (!grant.scope) { + grant.scope = 'oauth3_authn'; + } + var gscopes = grant.scope.split(/[+, ]/g); + gscopes.forEach(function (s) { grantedScopesMap[s] = true; }); + if (-1 !== gscopes.indexOf(requestedScope)) { + // already accepted in the past + acceptedScopes.push(requestedScope); + } + else { + // true, is pending + return true; + } + }); + }); + grantedScopes = Object.keys(grantedScopesMap); + + return { + pending: pendingScopes // not yet accepted + , granted: grantedScopes // all granted, ever + , requested: scopes // all requested, now + , accepted: acceptedScopes // granted (ever) and requested (now) + }; + }); + }; exports.OAUTH3_CORE_PROVIDER = core; diff --git a/oauth3.js b/oauth3.js index f2b8487..ae48c42 100644 --- a/oauth3.js +++ b/oauth3.js @@ -132,13 +132,43 @@ console.info('[oauth3.hooks.refreshSession] refreshedSession', oldSession); - return oauth3.PromiseA.resolve(oauth3.hooks.setSession(oldSession)); + // set for a set of audiences + return oauth3.PromiseA.resolve(oauth3.hooks.setSession(providerUri, oldSession)); } - , setSession: function (newSession) { + , setSession: function (providerUri, newSession) { + providerUri = oauth3.core.normalizeUri(providerUri); console.warn('[oauth3.hooks.setSession] PLEASE IMPLEMENT -- Your Fault'); console.warn(newSession); + if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; } + oauth3.hooks._sessions[providerUri] = newSession; return newSession; } + , getSession: function (providerUri) { + providerUri = oauth3.core.normalizeUri(providerUri); + console.warn('[oauth3.hooks.getSession] PLEASE IMPLEMENT -- Your Fault'); + if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; } + return oauth3.hooks._sessions[providerUri]; + } + + // Provider Only + , setGrants: function (clientUri, newGrants) { + clientUri = oauth3.core.normalizeUri(clientUri); + console.warn('[oauth3.hooks.setGrants] PLEASE IMPLEMENT -- Your Fault'); + console.warn(newGrants); + if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; } + console.log('clientUri, newGrants'); + console.log(clientUri, newGrants); + oauth3.hooks._grants[clientUri] = newGrants; + return newGrants; + } + , getGrants: function (clientUri) { + clientUri = oauth3.core.normalizeUri(clientUri); + console.warn('[oauth3.hooks.getGrants] PLEASE IMPLEMENT -- Your Fault'); + if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; } + console.log('clientUri, existingGrants'); + console.log(clientUri, oauth3.hooks._grants[clientUri]); + return oauth3.hooks._grants[clientUri]; + } }; // TODO simplify (nix recase) @@ -200,10 +230,43 @@ */ }; + // TODO merge with regular token access point and new response_type=federated ? + oauth3.requests.clientToken = function (providerUri, opts) { + return oauth3.discover(providerUri, opts).then(function (directive) { + return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) { + return grantsResult.originalData || grantsResult.data; + }); + }); + }; oauth3.requests.grants = function (providerUri, opts) { return oauth3.discover(providerUri, opts).then(function (directive) { console.log('core.urls.grants(directive, opts)', core.urls.grants(directive, opts)); - return oauth3.request(core.urls.grants(directive, opts)); + return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) { + if ('POST' === opts.method) { + // TODO this is clientToken + return grantsResult.originalData || grantsResult.data; + } + + var grants = grantsResult.originalData || grantsResult.data; + // TODO + if (grants.error) { + return oauth3.PromiseA.reject(oauth3.core.formatError(grants.error)); + } + + console.warn('requests.grants', grants); + + oauth3.hooks.setGrants(opts.client_id + '-client', grants.client); + grants.grants.forEach(function (grant) { + var clientId = grant.client_id || grant.oauth_client_id || grant.oauthClientId; + // TODO should save as an array + oauth3.hooks.setGrants(clientId, [ grant ]); + }); + + return { + client: oauth3.hooks.getGrants(opts.client_id + '-client') + , grants: oauth3.hooks.getGrants(opts.client_id) + }; + }); }); }; oauth3.requests.loginCode = function (providerUri, opts) { diff --git a/oauth3.provider.js b/oauth3.provider.js new file mode 100644 index 0000000..4af4083 --- /dev/null +++ b/oauth3.provider.js @@ -0,0 +1,94 @@ +;(function (exports) { + 'use strict'; + + var OAUTH3 = window.OAUTH3 || require('./oauth3.js'); + + OAUTH3.authz = OAUTH3.authz || {}; + OAUTH3.authz.scopes = function (providerUri, session, clientParams) { + // OAuth3.requests.grants(providerUri, {}); // return list of grants + // OAuth3.checkGrants(providerUri, {}); // + var clientUri = OAUTH3.core.normalizeUri(clientParams.client_id || clientParams.client_uri); + var scope = clientParams.scope || ''; + var clientObj = clientParams; + + if (!scope) { + scope = 'oauth3_authn'; + } + + return OAUTH3.requests.grants(providerUri, { + method: 'GET' + , client_id: clientUri + , client_uri: clientUri + , session: session + }).then(function (grants) { + var myGrants; + var grantedScopes; + var grantedScopesMap; + var pendingScopes; + var acceptedScopes; + var acceptedScopesMap; + var scopes = OAUTH3.core.parsescope(scope); + var callbackUrl; + + console.log('previous grants:'); + console.log(grants); + + // it doesn't matter who the referrer is as long as the destination + // is an authorized destination for the client in question + // (though it may not hurt to pass the referrer's info on to the client) + if (!OAUTH3.checkRedirect(grants.client, clientObj)) { + callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK' + + '?redirect_uri=' + clientObj.redirect_uri + + '&allowed_urls=' + grants.client.url + + '&client_id=' + clientUri + + '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer) + ; + location.href = callbackUrl; + return; + } + + myGrants = grants.grants.filter(function (grant) { + if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) { + return true; + } + }); + + grantedScopesMap = {}; + acceptedScopesMap = {}; + pendingScopes = scopes.filter(function (requestedScope) { + return myGrants.every(function (grant) { + if (!grant.scope) { + grant.scope = 'oauth3_authn'; + } + var gscopes = grant.scope.split(/[+, ]/g); + gscopes.forEach(function (s) { grantedScopesMap[s] = true; }); + if (-1 !== gscopes.indexOf(requestedScope)) { + // already accepted in the past + acceptedScopesMap[requestedScope] = true; + } + else { + // true, is pending + return true; + } + }); + }); + grantedScopes = Object.keys(grantedScopesMap); + acceptedScopes = Object.keys(acceptedScopesMap); + + return { + pending: pendingScopes // not yet accepted + , granted: grantedScopes // all granted, ever + , requested: scopes // all requested, now + , accepted: acceptedScopes // granted (ever) and requested (now) + , client: grants.client + , grants: grants.grants + }; + }); + }; + + exports.OAUTH3_PROVIDER = OAUTH3; + + if ('undefined' !== typeof module) { + module.exports = OAUTH3; + } +}('undefined' !== typeof exports ? exports : window));