diff --git a/oauth3.browser.js b/oauth3.browser.js index 856b08a..0c4e6b3 100644 --- a/oauth3.browser.js +++ b/oauth3.browser.js @@ -228,6 +228,201 @@ }); } } + , isIframe: function isIframe () { + try { + return window.self !== window.top; + } catch (e) { + return true; + } + } + , parseUrl: function (url) { + var parser = document.createElement('a'); + parser.href = url; + return parser; + } + , isRedirectHostSafe: function (referrerUrl, redirectUrl) { + var src = browser.parseUrl(referrerUrl); + var dst = browser.parseUrl(redirectUrl); + + // TODO how should we handle subdomains? + // It should be safe for api.example.com to redirect to example.com + // But it may not be safe for to example.com to redirect to aj.example.com + // It is also probably not safe for sally.example.com to redirect to john.example.com + // The client should have a list of allowed URLs to choose from and perhaps a wildcard will do + // + // api.example.com.evil.com SHOULD NOT match example.com + return dst.hostname !== src.hostname; + } + , checkRedirect: function (client, query) { + console.warn("[security] URL path checking not yet implemented"); + + var clientUrl = OAUTH3.core.normalizeUrl(client.url); + var redirectUrl = OAUTH3.core.normalizeUrl(query.redirect_uri); + + // General rule: + // I can callback to a shorter domain (fewer subs) or a shorter path (on the same domain) + // but not a longer (more subs) or different domain or a longer path (on the same domain) + + + // We can callback to an explicitly listed domain (TODO and path) + if (browser.isRedirectHostSafe(clientUrl, redirectUrl)) { + return true; + } + + return false; + } + /* + , redirect: function (redirect) { + if (parser.search) { + parser.search += '&'; + } else { + parser.search += '?'; + } + + parser.search += 'error=E_NO_SESSION'; + redirectUri = parser.href; + + window.location.href = redirectUri; + } + */ + + , hackFormSubmit: function (opts) { + opts = opts || {}; + scope.authorizationDecisionUri = DaplieApiConfig.providerUri + '/api/org.oauth3.provider/authorization_decision'; + scope.updateScope(); + + var redirectUri = scope.appQuery.redirect_uri.replace(/^https?:\/\//i, 'https://'); + var separator; + + // TODO check that we appropriately use '#' for implicit and '?' for code + // (server-side) in an OAuth2 backwards-compatible way + if ('token' === scope.appQuery.response_type) { + separator = '#'; + } + else if ('code' === scope.appQuery.response_type) { + separator = '?'; + } + else { + separator = '#'; + } + + if (scope.pendingScope.length && !opts.allow) { + redirectUri += separator + Oauth3.querystringify({ + error: 'access_denied' + , error_description: 'None of the permissions were accepted' + , error_uri: 'https://oauth3.org/docs/errors#access_denied' + , state: scope.appQuery.state + }); + $window.location.href = redirectUri; + return; + } + + // TODO move to Oauth3? or not? + // this could be implementation-specific, + // but it may still be nice to provide it as de-facto + var url = DaplieApiConfig.apiBaseUri + '/api/org.oauth3.provider/grants/:client_id/:account_id' + .replace(/:client_id/g, scope.appQuery.client_id || scope.appQuery.client_uri) + .replace(/:account_id/g, scope.selectedAccountId) + ; + + var account = scope.sessionAccount; + var session = { accessToken: account.token, refreshToken: account.refreshToken }; + var preq = { + url: url + , method: 'POST' + , data: { + scope: updateAccepted() + , response_type: scope.appQuery.response_type + , referrer: document.referrer || document.referer || '' + , referer: document.referrer || document.referer || '' + , tenant_id: scope.appQuery.tenant_id + , client_id: scope.appQuery.client_id + , client_uri: scope.appQuery.client_uri + } + , session: session + }; + preq.clientId = preq.appId = DaplieApiConfig.appId || DaplieApiConfig.clientId; + preq.clientUri = preq.appUri = DaplieApiConfig.appUri || DaplieApiConfig.clientUri; + console.log('hackFormSubmit preq', preq); + // TODO need a way to have middleware in Oauth3.request for TherapySession et al + return Oauth3.request(preq).then(function (resp) { + console.log('[DEBUG] grant code'); + console.log(resp); + + var err; + var data = resp.data || {}; + + if (data.error) { + err = new Error(data.error.message || data.errorDescription); + err.message = data.error.message || data.errorDescription; + err.code = resp.data.error.code || resp.data.error; + err.uri = 'https://oauth3.org/docs/errors#' + (resp.data.error.code || resp.data.error); + return $q.reject(err); + } + + if (!(data.code || data.accessToken)) { + err = new Error("No grant code"); + return $q.reject(err); + } + + return data; + }).then(function (data) { + redirectUri += separator + Oauth3.querystringify({ + state: scope.appQuery.state + + , code: data.code + + , access_token: data.accessToken + , expires_at: data.expiresAt + , expires_in: data.expiresIn + , scope: data.scope + + , refresh_token: data.refreshToken + , refresh_expires_at: data.refreshExpiresAt + , refresh_expires_in: data.refreshExpiresIn + }); + + if ('token' === scope.appQuery.response_type) { + $window.location.href = redirectUri; + return; + } + else if ('code' === scope.appQuery.response_type) { + scope.hackFormSubmitHelper(redirectUri); + return; + } + else { + console.warn("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'"); + console.warn(redirectUri); + throw new Error("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'"); + } + }, function (err) { + redirectUri += separator + Oauth3.querystringify({ + error: err.code || 'server_error' + , error_description: err.message || "Server Error: It's not your fault" + , error_uri: err.uri || 'https://oauth3.org/docs/errors#server_error' + , state: scope.appQuery.state + }); + + console.error('Grant Code Error NOT IMPLEMENTED'); + console.error(err); + console.error(redirectUri); + //$window.location.href = redirectUri; + }); + } + + , hackFormSubmitHelper: function (uri) { + // TODO de-jQuerify + //$window.location.href = redirectUri; + //return; + + // the only way to do a POST that redirects the current window + window.jQuery('form.js-hack-hidden-form').attr('action', uri); + + // give time for the apply to take place + window.setTimeout(function () { + window.jQuery('form.js-hack-hidden-form').submit(); + }, 50); + } }; Object.keys(browser).forEach(function (key) {