예제 #1
0
        private bool Parse_FirstLeg_Authentication_Content(
            string _ResponseContent,
            out string _AuthorizationCode_From_FirstLeg,
            out BMemoryQueryParameters _SSOStateUniqueID_QueryParameters,
            out SSOStateMEntry _SSOState,
            out string _LocalRedirectUrl_From_FirstLeg,
            out string _EmailAddress_From_FirstLeg,
            out string _AzureADUniqueID_From_FirstLeg,
            out BWebServiceResponse _FailureResponse,
            Action <string> _ErrorMessageAction)
        {
            _AuthorizationCode_From_FirstLeg  = null;
            _SSOStateUniqueID_QueryParameters = new BMemoryQueryParameters();
            _SSOState = null;
            _LocalRedirectUrl_From_FirstLeg = null;
            _EmailAddress_From_FirstLeg     = null;
            _AzureADUniqueID_From_FirstLeg  = null;
            _FailureResponse = BWebResponse.InternalError("");

            _ResponseContent = _ResponseContent.Trim();

            //Handle error
            if (_ResponseContent.StartsWith("error="))
            {
                var ErrorResponse = new JObject()
                {
                    ["result"] = "failure"
                };
                try
                {
                    var ErrorFields = _ResponseContent.Split('&');
                    if (ErrorFields != null && ErrorFields.Length >= 2)
                    {
                        ErrorResponse["error"]   = ErrorFields[0].Substring("error=".Length);
                        ErrorResponse["message"] = ErrorFields[1].Substring("error_description=".Length);
                    }
                }
                catch (Exception) { }

                _FailureResponse = BWebResponse.Unauthorized(ErrorResponse.ToString());
                return(false);
            }

            //Normal flow
            var Splitted = _ResponseContent.Split('&');

            if (Splitted == null || Splitted.Length < 3)
            {
                _FailureResponse = BWebResponse.BadRequest("Request body must contain all requested types. Split has failed.");
                return(false);
            }

            string IDToken    = null;
            string StateField = null;

            for (var i = 0; i < Splitted.Length; i++)
            {
                if (Splitted[i].StartsWith("id_token="))
                {
                    IDToken = Splitted[i].Substring("id_token=".Length);
                }
                else if (Splitted[i].StartsWith("code="))
                {
                    _AuthorizationCode_From_FirstLeg = Splitted[i].Substring("code=".Length);
                }
                else if (Splitted[i].StartsWith("state="))
                {
                    StateField = WebUtility.UrlDecode(Splitted[i].Substring("state=".Length));
                }
            }
            if (IDToken == null || _AuthorizationCode_From_FirstLeg == null || StateField == null)
            {
                _FailureResponse = BWebResponse.BadRequest("Request body must contain all requested types.");
                return(false);
            }

            Splitted = StateField.Split('&');
            if (Splitted == null || Splitted.Length < 3)
            {
                _FailureResponse = BWebResponse.BadRequest("State field must contain all mandatory entries. Split has failed.");
                return(false);
            }

            bool   bSSOStateUniqueID_QueryParameters_Set = false;
            string TenantName = null;

            for (var i = 0; i < Splitted.Length; i++)
            {
                if (Splitted[i].StartsWith("redirect_url="))
                {
                    _LocalRedirectUrl_From_FirstLeg = WebUtility.UrlDecode(Splitted[i].Substring("redirect_url=".Length));
                }
                else if (Splitted[i].StartsWith("tenant="))
                {
                    TenantName = Splitted[i].Substring("tenant=".Length);
                }
                else if (Splitted[i].StartsWith("state="))
                {
                    _SSOStateUniqueID_QueryParameters     = SSOStateMEntry.ID_SSO_STATE_MEMORY_SERVICE_KEY(Splitted[i].Substring("state=".Length));
                    bSSOStateUniqueID_QueryParameters_Set = true;
                }
            }
            if (_LocalRedirectUrl_From_FirstLeg == null || TenantName == null || !bSSOStateUniqueID_QueryParameters_Set)
            {
                _FailureResponse = BWebResponse.BadRequest("State field must contain all mandatory entries.");
                return(false);
            }

            var Serialized = MemoryService.GetKeyValue(_SSOStateUniqueID_QueryParameters, SSOStateMEntry.HASH_KEY, _ErrorMessageAction);

            if (Serialized == null)
            {
                _FailureResponse = BWebResponse.Unauthorized("Login prompt session has expired. Please try again.");
                return(false);
            }
            try
            {
                _SSOState = JsonConvert.DeserializeObject <SSOStateMEntry>(Serialized.AsString);
                if (_SSOState == null)
                {
                    throw new NullReferenceException();
                }
            }
            catch (Exception e)
            {
                _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Parse_FirstLeg_Authentication_Content: Invalid session state. Message: " + e.Message + ", trace: " + e.StackTrace);
                _FailureResponse = BWebResponse.InternalError("Invalid session state. Please try again.");
                return(false);
            }
            if (_SSOState.Status != SSOStateMEntry.STATUS_AUTHENTICATING)
            {
                _FailureResponse = BWebResponse.Unauthorized("Invalid SSO state. Please try again.");
                return(false);
            }
            if (TenantName != _SSOState.TenantName)
            {
                _FailureResponse = BWebResponse.Unauthorized("SSO state - request tenant mismatch. Please try again.");
                return(false);
            }

            var JWTHandler         = new JwtSecurityTokenHandler();
            JwtSecurityToken Token = null;

            try
            {
                Token = JWTHandler.ReadJwtToken(IDToken);
            }
            catch (Exception e)
            {
                _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Parse_FirstLeg_Authentication_Content: Invalid JWT token. Token: " + IDToken + ", message: " + e.Message + ", trace: " + e.StackTrace);
                _FailureResponse = BWebResponse.BadRequest("Invalid JWT token.");
                return(false);
            }

            if (!Token.Payload.TryGetValue("email", out object EmailObject))
            {
                _FailureResponse = BWebResponse.BadRequest("JWT token does not contain email in the payload.");
                return(false);
            }

            _EmailAddress_From_FirstLeg = ((string)EmailObject).ToLower();

            if (!Token.Payload.TryGetValue("sub", out object AzureADUserUniqueIDObject))
            {
                _FailureResponse = BWebResponse.BadRequest("JWT token does not contain sub in the payload.");
                return(false);
            }
            _AzureADUniqueID_From_FirstLeg = ((string)AzureADUserUniqueIDObject).ToLower();

            return(true);
        }
예제 #2
0
        private bool Perform_SecondLeg_Authorization(
            string _AuthorizationCode_From_FirstLeg,
            BMemoryQueryParameters _SSOStateUniqueID_QueryParameters,
            SSOStateMEntry _SSOState,
            out AuthorizationResult _SuccessResponse,
            out BWebServiceResponse _FailureResponse,
            Action <string> _ErrorMessageAction)
        {
            _SuccessResponse = null;
            _FailureResponse = BWebResponse.InternalError("");

            _SSOState.Status = SSOStateMEntry.STATUS_AUTHORIZING;
            MemoryService.SetKeyValue(_SSOStateUniqueID_QueryParameters,
                                      new Tuple <string, BPrimitiveType>[]
            {
                new Tuple <string, BPrimitiveType>(SSOStateMEntry.HASH_KEY, new BPrimitiveType(JsonConvert.SerializeObject(_SSOState)))
            },
                                      _ErrorMessageAction);
            MemoryService.SetKeyExpireTime(_SSOStateUniqueID_QueryParameters, TimeSpan.FromSeconds(120), _ErrorMessageAction);

            var FormUrlEncodedPairs = new List <KeyValuePair <string, string> >()
            {
                new KeyValuePair <string, string>("client_id", AzureAD_AppID),
                new KeyValuePair <string, string>("scope", SSOCommon.SCOPE),
                new KeyValuePair <string, string>("grant_type", "authorization_code"),
                new KeyValuePair <string, string>("code", _AuthorizationCode_From_FirstLeg),
                new KeyValuePair <string, string>("redirect_uri", WebUtility.UrlDecode(_SSOState.ServersideRedirectUrl)),
                new KeyValuePair <string, string>("client_secret", WebUtility.UrlDecode(AzureAD_ClientSecret))
            };

            using var Handler = new HttpClientHandler
                  {
                      SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
                      ServerCertificateCustomValidationCallback = (a, b, c, d) => true
                  };
            using var Client = new HttpClient(Handler);
            Client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");

            string ResponseString = null;

            try
            {
                using var RequestContent = new FormUrlEncodedContent(FormUrlEncodedPairs);
                using var RequestTask    = Client.PostAsync("https://login.microsoftonline.com/common/oauth2/v2.0/token", RequestContent);
                RequestTask.Wait();

                using var Response        = RequestTask.Result;
                using var ResponseContent = Response.Content;

                using var ReadResponseTask = ResponseContent.ReadAsStringAsync();
                ReadResponseTask.Wait();

                ResponseString = ReadResponseTask.Result;

                if (!Response.IsSuccessStatusCode)
                {
                    bool bJsonParseable = true;
                    try { JObject.Parse(ResponseString); } catch (JsonReaderException) { bJsonParseable = false; }

                    _FailureResponse = new BWebServiceResponse(
                        (int)Response.StatusCode,
                        new BStringOrStream(ResponseString),
                        bJsonParseable ? "application/json" : "text/html");
                    return(false);
                }

                var Parsed = JObject.Parse(ResponseString);
                _SuccessResponse = new AuthorizationResult()
                {
                    TokenType        = (string)Parsed["token_type"],
                    AccessToken      = (string)Parsed["access_token"],
                    ExpiresInSeconds = (int)Parsed["expires_in"],
                    RefreshToken     = (string)Parsed["refresh_token"]
                };
            }
            catch (Exception e)
            {
                if (e.InnerException != null && e.InnerException != e)
                {
                    _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Perform_SecondLeg_Authorization->Inner: " + e.InnerException.Message + ", Trace: " + e.InnerException.StackTrace);
                }
                if (e is AggregateException)
                {
                    foreach (var Inner in (e as AggregateException).InnerExceptions)
                    {
                        _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Perform_SecondLeg_Authorization->Aggregate->Inner: " + Inner.Message + ", Trace: " + Inner.StackTrace);
                    }
                }
                _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Perform_SecondLeg_Authorization: Authorization request failed. Response: " + ResponseString + ", message: " + e.Message + ", trace: " + e.StackTrace);
                _FailureResponse = BWebResponse.InternalError("Authorization request has failed.");
                return(false);
            }

            MemoryService.DeleteAllKeys(_SSOStateUniqueID_QueryParameters, true, _ErrorMessageAction);

            return(true);
        }
예제 #3
0
        private BWebServiceResponse OnRequest_Internal(HttpListenerContext _Context, Action <string> _ErrorMessageAction = null)
        {
            if (!UrlParameters.TryGetValue("redirect_url", out string RedirectUrlEncoded) || RedirectUrlEncoded.Length == 0)
            {
                RedirectUrlEncoded = DEFAULT_REDIRECT_URL_ENCODED;
            }

            if (_Context.Request.HttpMethod != "GET")
            {
                _ErrorMessageAction?.Invoke("SSOLoginRequest: GET method is accepted. But received request method:  " + _Context.Request.HttpMethod);

                return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, BWebResponse.Error_BadRequest_Code, "GET method is accepted. But received request method:  " + _Context.Request.HttpMethod));
            }

            if (!UrlParameters.TryGetValue("tenant", out string TenantName) || TenantName.Length == 0)
            {
                TenantName = DEFAULT_TENANT_NAME;
            }
            else
            {
                TenantName = TenantName.ToLower();
            }

            //Existing token from header
            string ClientAuthorization = null;

            if (BWebUtilities.DoesContextContainHeader(out List <string> ClientAuthorizationHeaderValues, out string _, _Context, "client-authorization"))
            {
                BUtility.CheckAndGetFirstStringFromList(ClientAuthorizationHeaderValues, out ClientAuthorization);
                if (ClientAuthorization != null && ClientAuthorization.Length == 0)
                {
                    ClientAuthorization = null;
                }
            }

            //Existing token from url parameters
            //Note: Must be token type prepended. Example: ?existing_token=bearer%20abc123123
            if (!UrlParameters.TryGetValue("existing_token", out string ExistingToken) || ExistingToken.Length == 0)
            {
                ExistingToken = null;
            }
            else
            {
                ExistingToken = WebUtility.UrlDecode(ExistingToken);
            }

            //If both existing tokens are non-null; it is error
            if (ClientAuthorization != null && ExistingToken != null)
            {
                _ErrorMessageAction?.Invoke("Error: SSOLoginRequest: Both existing tokens from url parameters and headers are non-null.");

                return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, BWebResponse.Error_BadRequest_Code, "Both existing tokens from url parameters and headers are non-null."));
            }

            //From now on, use ClientAuthorization; not ExistingToken
            if (ExistingToken != null)
            {
                ClientAuthorization = ExistingToken;
            }

            //Check and try refresh if expired
            if (ClientAuthorization != null &&
                new Controller_SSOAccessToken(ClientAuthorization, DatabaseService, MemoryService, AzureAD_AppID, AzureAD_ClientSecret, SSOSuperAdmins, _ErrorMessageAction)
                .PerformCheckAndRefresh(
                    out Controller_SSOAccessToken.EPerformCheckAndRefreshSuccessStatus _,
                    out ClientAuthorization,
                    out string UserID,
                    out string _))
            {
                return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), false, 0, null, UserID, ClientAuthorization));
            }

            //Get api passthrough endpoint from internal set state
            var LocalErrorString = "";

            if (!InternalSetState.GetValueFromMemoryService(
                    out string ApiPassthroughEndpoint,
                    InternalSetState.API_PASSTHROUGH_PUBLIC_ENDPOINT_PROPERTY,
                    MemoryService,
                    (string _Message) =>
            {
                LocalErrorString = _Message;
                _ErrorMessageAction?.Invoke(_Message);
            }))
            {
                return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, 500, LocalErrorString));
            }

            string ServersideRedirectUrl = WebUtility.UrlEncode(ApiPassthroughEndpoint + "/auth/login/azure/callback");

            string AzureAuthenticationEndpointBase =
                "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
                + "?client_id=" + AzureAD_AppID
                + "&response_type=id_token code"
                + "&redirect_uri=" + ServersideRedirectUrl;

            var    TrialCount = 0;
            string SSOStateUniqueID;
            BMemoryQueryParameters SSOStateUniqueID_QueryParameters;

            do
            {
                if (!BUtility.CalculateStringMD5(BUtility.RandomString(32, true), out SSOStateUniqueID, _ErrorMessageAction))
                {
                    return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, 500, "SSO State ID generation has failed."));
                }

                SSOStateUniqueID_QueryParameters = SSOStateMEntry.ID_SSO_STATE_MEMORY_SERVICE_KEY(SSOStateUniqueID);

                if (!MemoryService.SetKeyValueConditionally(
                        SSOStateUniqueID_QueryParameters,
                        new Tuple <string, BPrimitiveType>(
                            SSOStateMEntry.HASH_KEY,
                            new BPrimitiveType(JsonConvert.SerializeObject(
                                                   new SSOStateMEntry()
                {
                    ServersideRedirectUrl = ServersideRedirectUrl,
                    TenantName = TenantName,
                    Status = SSOStateMEntry.STATUS_AUTHENTICATING
                })
                                               )
                            ),
                        _ErrorMessageAction))
                {
                    SSOStateUniqueID = null;
                }
            } while (SSOStateUniqueID == null && ++TrialCount < 5);

            if (SSOStateUniqueID == null)
            {
                return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, 500, "Unique SSO State ID generation has failed."));
            }
            MemoryService.SetKeyExpireTime(SSOStateUniqueID_QueryParameters, TimeSpan.FromSeconds(120), _ErrorMessageAction);

            var AzureAuthenticationEndpoint = AzureAuthenticationEndpointBase
                                              + "&scope=" + SSOCommon.SCOPE_URL_ENCODED
                                              + "&response_mode=form_post"
                                              + "&nonce=" + SSOStateUniqueID
                                              + "&state="
                                              + WebUtility.UrlEncode(
                "redirect_url=" + RedirectUrlEncoded +
                "&tenant=" + TenantName +
                "&state=" + SSOStateUniqueID);

            return(SSOCommon.MakeCallerRedirected(AzureAuthenticationEndpoint, false, 0, null));
        }