Beispiel #1
0
        /// <summary>
        /// Called when a server requests user authorization
        /// </summary>
        /// <param name="sender">Server of type <see cref="Server"/> requiring authorization</param>
        /// <param name="e">Authorization request event arguments</param>
        public void OnRequestAuthorization(object sender, RequestAuthorizationEventArgs e)
        {
            if (!(sender is Server authenticatingServer))
            {
                return;
            }

            e.TokenOrigin = RequestAuthorizationEventArgs.TokenOriginType.None;
            e.AccessToken = null;

            lock (Properties.Settings.Default.AccessTokenCache)
            {
                if (e.SourcePolicy != RequestAuthorizationEventArgs.SourcePolicyType.ForceAuthorization)
                {
                    var key = authenticatingServer.Base.AbsoluteUri;
                    if (Properties.Settings.Default.AccessTokenCache.TryGetValue(key, out var accessToken))
                    {
                        if (!e.ForceRefresh && DateTimeOffset.Now < accessToken.Expires)
                        {
                            e.TokenOrigin = RequestAuthorizationEventArgs.TokenOriginType.Saved;
                            e.AccessToken = accessToken;
                            return;
                        }

                        // Token refresh was explicitly requested or the token expired. Refresh it.
                        if (accessToken is InvalidToken)
                        {
                            // Invalid token is not refreshable.
                            Properties.Settings.Default.AccessTokenCache.Remove(key);
                        }
                        else
                        {
                            // Get API endpoints. (Not called from the UI thread or already cached by now. Otherwise it would need to be spawned as a background task to avoid deadlock.)
                            var api = authenticatingServer.GetEndpoints(Window.Abort.Token);

                            // Prepare web request.
                            var request = Xml.Response.CreateRequest(api.TokenEndpoint);
                            try
                            {
                                accessToken = accessToken.RefreshToken(request, null, Window.Abort.Token);

                                // Update access token cache.
                                Properties.Settings.Default.AccessTokenCache[key] = accessToken;

                                // If we got here, return the token.
                                e.TokenOrigin = RequestAuthorizationEventArgs.TokenOriginType.Refreshed;
                                e.AccessToken = accessToken;
                                return;
                            }
                            catch (AccessTokenException ex)
                            {
                                if (ex.ErrorCode == AccessTokenException.ErrorCodeType.InvalidGrant)
                                {
                                    // The grant has been revoked. Drop the access token.
                                    Properties.Settings.Default.AccessTokenCache.Remove(key);
                                }
                                else
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }

                if (e.SourcePolicy != RequestAuthorizationEventArgs.SourcePolicyType.SavedOnly)
                {
                    AuthorizationInProgress = new CancellationTokenSource();
                    Wizard.TryInvoke((Action)(() =>
                    {
                        Wizard.TaskCount++;
                        ReturnPage = Wizard.CurrentPage;
                        Wizard.CurrentPage = this;
                    }));
                    try
                    {
                        // Get API endpoints. (Not called from the UI thread. Otherwise it would need to be spawned as a background task to avoid deadlock.)
                        var api = authenticatingServer.GetEndpoints(Window.Abort.Token);

                        // Prepare new authorization grant.
                        AuthorizationGrant authorizationGrant = null;
                        Uri callbackUri  = null;
                        var httpListener = new eduOAuth.HttpListener(IPAddress.Loopback, 0);
                        httpListener.HttpCallback += (object _, HttpCallbackEventArgs eHTTPCallback) =>
                        {
                            callbackUri = eHTTPCallback.Uri;
                            AuthorizationInProgress.Cancel();
                            Wizard.TryInvoke((Action)(() => Wizard.CurrentPage = ReturnPage));
                        };
                        httpListener.HttpRequest += (object _, HttpRequestEventArgs eHTTPRequest) =>
                        {
                            if (eHTTPRequest.Uri.AbsolutePath.ToLowerInvariant() == "/favicon.ico")
                            {
                                var res = System.Windows.Application.GetResourceStream(new Uri("pack://*****:*****@RETURN_TO@", HttpUtility.UrlEncode(authorizationUri.ToString()))
                                                           .Replace("@ORG_ID@", HttpUtility.UrlEncode(srv.OrganizationId)));
                            }

                            // Trigger authorization.
                            Process.Start(authorizationUri.ToString());

                            // Wait for a change: either callback is invoked, either user cancels.
                            CancellationTokenSource.CreateLinkedTokenSource(AuthorizationInProgress.Token, Window.Abort.Token).Token.WaitHandle.WaitOne();
                        }
                        finally
                        {
                            // Delay HTTP server shutdown allowing browser to finish loading content.
                            new Thread(new ThreadStart(() =>
                            {
                                Window.Abort.Token.WaitHandle.WaitOne(5 * 1000);
                                httpListener.Stop();
                            })).Start();
                        }

                        if (callbackUri == null)
                        {
                            throw new OperationCanceledException();
                        }

                        // Get access token from authorization grant.
                        var request = Xml.Response.CreateRequest(api.TokenEndpoint);
                        e.AccessToken = authorizationGrant.ProcessResponse(
                            HttpUtility.ParseQueryString(callbackUri.Query),
                            request,
                            null,
                            Window.Abort.Token);
                        Window.Abort.Token.ThrowIfCancellationRequested();

                        // Save access token to the cache.
                        e.TokenOrigin = RequestAuthorizationEventArgs.TokenOriginType.Authorized;
                        Properties.Settings.Default.AccessTokenCache[authenticatingServer.Base.AbsoluteUri] = e.AccessToken;
                    }
                    finally { Wizard.TryInvoke((Action)(() => Wizard.TaskCount--)); }
                }
            }
        }
Beispiel #2
0
        public void AuthorizationGrantTest()
        {
            var ag = new AuthorizationGrant(
                new Uri("https://test.eduvpn.org/?param=1"),
                new Uri("org.eduvpn.app:/api/callback"),
                "org.eduvpn.app",
                new HashSet <string>()
            {
                "scope1", "scope2"
            });

            var uriBuilder = new UriBuilder(ag.AuthorizationUri);

            Assert.AreEqual("https", uriBuilder.Scheme);
            Assert.AreEqual("test.eduvpn.org", uriBuilder.Host);
            Assert.AreEqual("/", uriBuilder.Path);

            var query = HttpUtility.ParseQueryString(uriBuilder.Query);

            Assert.AreEqual("1", query["param"]);
            Assert.AreEqual("code", query["response_type"]);
            Assert.AreEqual("org.eduvpn.app", query["client_id"]);
            Assert.AreEqual("org.eduvpn.app:/api/callback", query["redirect_uri"]);
            CollectionAssert.AreEqual(new List <string>()
            {
                "scope1", "scope2"
            }, query["scope"].Split(null));
            Assert.IsTrue(AuthorizationGrant.Base64UriDecodeNoPadding(query["state"]).Length > 0);
            Assert.AreEqual("S256", query["code_challenge_method"]);
            Assert.IsTrue(AuthorizationGrant.Base64UriDecodeNoPadding(query["code_challenge"]).Length > 0);

            var request = new Mock <HttpWebRequest>();

            request.Setup(obj => obj.RequestUri).Returns(new Uri("https://demo.eduvpn.nl/portal/oauth.php/token"));
            request.SetupSet(obj => obj.Method = "POST");
            request.SetupProperty(obj => obj.Credentials);
            request.SetupProperty(obj => obj.PreAuthenticate, false);
            request.SetupSet(obj => obj.ContentType = "application/x-www-form-urlencoded");
            request.SetupProperty(obj => obj.ContentLength);
            var request_buffer = new byte[1048576];

            request.Setup(obj => obj.GetRequestStream()).Returns(new MemoryStream(request_buffer, true));
            var response = new Mock <HttpWebResponse>();

            response.Setup(obj => obj.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(Global.AccessTokenJSON)));
            response.SetupGet(obj => obj.StatusCode).Returns(HttpStatusCode.OK);
            request.Setup(obj => obj.GetResponse()).Returns(response.Object);

            AccessToken
                token1 = new BearerToken(Global.AccessTokenObj, DateTimeOffset.Now),
                token2 = ag.ProcessResponse(new NameValueCollection()
            {
                { "state", query["state"] }, { "code", "1234567890" }
            }, request.Object, new NetworkCredential("", "password").SecurePassword);
            var request_param = HttpUtility.ParseQueryString(Encoding.ASCII.GetString(request_buffer, 0, (int)request.Object.ContentLength));

            Assert.AreEqual("authorization_code", request_param["grant_type"]);
            Assert.IsNotNull(request_param["code"]);
            Assert.AreEqual(ag.RedirectEndpoint, request_param["redirect_uri"]);
            Assert.AreEqual(ag.ClientId, request_param["client_id"]);
            Assert.IsNotNull(request_param["code_verifier"]);
            Assert.AreEqual(token1, token2);
            Assert.IsTrue((token1.Authorized - token2.Authorized).TotalSeconds < 60);
            Assert.IsTrue((token1.Expires - token2.Expires).TotalSeconds < 60);
            Assert.IsTrue(token2.Scope != null);
            Assert.IsTrue(token1.Scope.SetEquals(token2.Scope));

            Assert.ThrowsException <eduJSON.MissingParameterException>(() => ag.ProcessResponse(new NameValueCollection()
            {
                { "code", "1234567890" }
            }, request.Object));
            Assert.ThrowsException <eduJSON.MissingParameterException>(() => ag.ProcessResponse(new NameValueCollection()
            {
                { "state", query["state"] }
            }, request.Object));
            Assert.ThrowsException <InvalidStateException>(() => ag.ProcessResponse(new NameValueCollection()
            {
                { "state", AuthorizationGrant.Base64UrlEncodeNoPadding(new byte[] { 0x01, 0x02, 0x03 }) }, { "code", "1234567890" }
            }, request.Object));
            Assert.ThrowsException <AuthorizationGrantException>(() => ag.ProcessResponse(new NameValueCollection()
            {
                { "state", query["state"] }, { "error", "error" }, { "code", "1234567890" }
            }, request.Object));
            Assert.ThrowsException <eduJSON.MissingParameterException>(() => ag.ProcessResponse(new NameValueCollection()
            {
                { "state", query["state"] }
            }, request.Object));
        }