private async Task <bool> ValidateAuthorizationHeader(AuthenticationHeaderValue authHeader, string targetUrl, string userId)
        {
            // Validate that we have a bearer token
            if (authHeader == null ||
                !string.Equals(authHeader.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(authHeader.Parameter))
            {
                return(false);
            }

            // Validate the token
            ActionableMessageTokenValidator        validator = new ActionableMessageTokenValidator();
            ActionableMessageTokenValidationResult result    = await validator.ValidateTokenAsync(authHeader.Parameter, targetUrl);

            if (!result.ValidationSucceeded)
            {
                return(false);
            }

            // Token is valid, now check the sender and action performer
            // Both should equal the user
            if (!string.Equals(result.ActionPerformer, userId, StringComparison.OrdinalIgnoreCase) ||
                !string.Equals(result.Sender, string.IsNullOrEmpty(amSender) ? userId : amSender, StringComparison.OrdinalIgnoreCase))
            {
                return(false);
            }

            return(true);
        }
Example #2
0
        public async Task TestInvalidTokens()
        {
            ActionableMessageTokenValidator        validator = new ActionableMessageTokenValidator();
            ActionableMessageTokenValidationResult result    = await validator.ValidateTokenAsync("abc", "https://www.microsoft.com");

            Assert.False(result.ValidationSucceeded);
        }
Example #3
0
        /// <summary>
        /// The POST method for the expense controller.
        /// </summary>
        /// <param name="value">Value from the POST request body.</param>
        /// <returns>The asynchronous task.</returns>
        // POST api/expense
        public async Task <HttpResponseMessage> Post([FromBody] string value)
        {
            HttpRequestMessage request = this.ActionContext.Request;

            // Validate that we have a bearer token.
            if (request.Headers.Authorization == null ||
                !string.Equals(request.Headers.Authorization.Scheme, BearerTokenType, StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
            {
                return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()));
            }

            string bearerToken = request.Headers.Authorization.Parameter;
            ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator();

            // ValidateTokenAsync will verify the following
            // 1. The token is issued by Microsoft and its digital signature is valid.
            // 2. The token has not expired.
            // 3. The audience claim matches the service domain URL.
            //
            // Replace https://api.contoso.com with your service domain URL.
            // For example, if the service URL is https://api.xyz.com/finance/expense?id=1234,
            // then replace https://api.contoso.com with https://api.xyz.com
            ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, "https://api.contoso.com");

            if (!result.ValidationSucceeded)
            {
                if (result.Exception != null)
                {
                    Trace.TraceError(result.Exception.ToString());
                }

                return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()));
            }

            // We have a valid token. We will verify the sender and the action performer.
            // You should replace the code below with your own validation logic.
            // In this example, we verify that the email is sent by [email protected]
            // and the action performer has to be someone with @contoso.com email.
            //
            // You should also return the CARD-ACTION-STATUS header in the response.
            // The value of the header will be displayed to the user.
            if (!string.Equals(result.Sender, @"*****@*****.**", StringComparison.OrdinalIgnoreCase) ||
                !result.ActionPerformer.ToLower().EndsWith("@contoso.com"))
            {
                HttpResponseMessage errorResponse = request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError());
                errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed.");
                return(errorResponse);
            }

            // Further business logic code here to process the expense report.

            HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);

            response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved.");
            return(response);
        }
        public static async Task <IActionResult> Run(
#pragma warning disable RCS1163 // Unused parameter.
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
#pragma warning restore RCS1163 // Unused parameter.
            [ActionableMessageValidator] ActionableMessageTokenValidationResult tokenValidation,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            return(new OkObjectResult(1));
        }
Example #5
0
    /// <summary>
    /// The POST method for the ticket controller.
    /// </summary>
    /// <param name="cardResponse">Value from the POST request body.</param>
    /// <returns>The asynchronous task.</returns>
    // POST api/ticket
    public async Task <HttpResponseMessage> Post(CardResponse cardResponse)
    {
        HttpRequestMessage request = this.ActionContext.Request;

        // Validate that we have a bearer token.
        if (request.Headers.Authorization == null ||
            !string.Equals(request.Headers.Authorization.Scheme, BearerTokenType, StringComparison.OrdinalIgnoreCase) ||
            string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
        {
            return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()));
        }

        string bearerToken = request.Headers.Authorization.Parameter;
        ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator();

        // ValidateTokenAsync will verify the following
        // 1. The token is issued by Microsoft and its digital signature is valid.
        // 2. The token has not expired.
        // 3. The audience claim matches the service domain URL.
        ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, WebServiceHost);

        if (!result.ValidationSucceeded)
        {
            if (result.Exception != null)
            {
                Trace.TraceError(result.Exception.ToString());
            }

            return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()));
        }

        // We have a valid token. Your application should verify the sender and/or the ActionPerformer
        //
        // You should also return the CARD-ACTION-STATUS header in the response.
        // The value of the header will be displayed to the user.
        if (!result.Sender.ToLowerInvariant().EndsWith(SenderEmailDomain.ToLowerInvariant()))
        {
            HttpResponseMessage errorResponse = request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError());
            errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed.");
            return(errorResponse);
        }

        // prepare the response
        HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);

        response.Headers.Add("CARD-ACTION-STATUS", "Comment recorded...");

        #region Business logic code here to process the support ticket.
        #endregion

        return(response);
    }
Example #6
0
        public async Task TestValidToken()
        {
            var testCert     = GetTestCert();
            var openIdConfig = GetTestO365OpenIdConnectConfiguration(testCert);

            CancellationToken cancelToken;
            Mock <IConfigurationManager <OpenIdConnectConfiguration> > mockConfigManager = new Mock <IConfigurationManager <OpenIdConnectConfiguration> >();

            mockConfigManager.Setup(cm => cm.GetConfigurationAsync(cancelToken)).ReturnsAsync(openIdConfig);

            ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(mockConfigManager.Object);
            string token = CertificateHelper.GenerateJsonWebToken(testCert, "*****@*****.**", "*****@*****.**", "https://api.contoso.com");
            ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(token, "https://api.contoso.com");

            Assert.True(result.ValidationSucceeded);
            Assert.Equal("*****@*****.**", result.ActionPerformer);
            Assert.Equal("*****@*****.**", result.Sender);
        }
        public async Task <IValueProvider> BindAsync(BindingContext context)
        {
            var validator = new ActionableMessageTokenValidator();
            ActionableMessageTokenValidationResult result = null;

            if (context.BindingData.TryGetValue("$request", out var request))
            {
                var req = (HttpRequest)request;
                if (req.Headers.TryGetValue("Authorization", out var authHeader))
                {
                    var token = authHeader.FirstOrDefault()?.Substring("Bearer ".Length)?.Trim();
                    if (!string.IsNullOrEmpty(token))
                    {
                        var serviceUrl = $"{req.Scheme}://{req.Host}";
                        result = await validator.ValidateTokenAsync(token, serviceUrl);
                    }
                }
            }

            return(await BindAsync(result, context.ValueContext));
        }
        public static async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
            HttpRequest request,
            ILogger log)
        {
            log.LogInformation($"Excuting {nameof(AuthorizationTest)}.");

            var bearerToken = request.Headers["Authorization"].ToString();

            log.LogDebug($"Found bearer token `{bearerToken}`");

            var baseUrl = $"{request.Scheme}{Uri.SchemeDelimiter}{request.Host.Value}";

            var validationResult = new ActionableMessageTokenValidationResult();

            try
            {
                var tokenValidator = new ActionableMessageTokenValidator();
                validationResult = await tokenValidator.ValidateTokenAsync(bearerToken.Replace("Bearer ", ""), baseUrl);
            }
            catch (Exception ex)
            {
                log.LogError(ex, "Validation failed");
            }

            if (validationResult.ValidationSucceeded)
            {
                log.LogInformation($"Token is valid! With sender `{validationResult.Sender}` and performer `{validationResult.ActionPerformer}`.");
            }
            else
            {
                log.LogWarning($"{validationResult.Exception}");
            }


            log.LogInformation($"Excuted {nameof(AuthorizationTest)}.");

            return(new OkResult());
        }
        public static async Task <string> ValidateAuthorizationHeader(AuthenticationHeaderValue authHeader)
        {
            // Validate that we have a bearer token
            if (authHeader == null ||
                !string.Equals(authHeader.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(authHeader.Parameter))
            {
                return(string.Empty);
            }

            // Validate the token
            ActionableMessageTokenValidator        validator = new ActionableMessageTokenValidator();
            ActionableMessageTokenValidationResult result    = await validator.ValidateTokenAsync(authHeader.Parameter, merchantId);

            if (!result.ValidationSucceeded)
            {
                return(string.Empty);
            }

            // Token is valid, return the action performer, which is the email
            // address of the user that took the action. This should match
            // the email that you sent the invoice to
            return(result.ActionPerformer);
        }
Example #10
0
        /// <summary>
        /// The POST method for the ticket controller.
        /// </summary>
        /// <param name="cardResponse">Value from the POST request body.</param>
        /// <returns>The asynchronous task.</returns>
        // POST api/ticket
        public async Task <HttpResponseMessage> Post(Models.CardResponse cardResponse)
        {
            HttpRequestMessage request = this.ActionContext.Request;

            // Validate that we have a bearer token.
            if (request.Headers.Authorization == null ||
                !string.Equals(request.Headers.Authorization.Scheme, BearerTokenType, StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
            {
                return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()));
            }

            string bearerToken = request.Headers.Authorization.Parameter;
            ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator();

            // ValidateTokenAsync will verify the following
            // 1. The token is issued by Microsoft and its digital signature is valid.
            // 2. The token has not expired.
            // 3. The audience claim matches the service domain URL.
            ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, WebServiceHost);

            if (!result.ValidationSucceeded)
            {
                if (result.Exception != null)
                {
                    Trace.TraceError(result.Exception.ToString());
                }

                return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()));
            }

            // We have a valid token. Your application should verify the sender and/or the ActionPerformer
            //
            // You should also return the CARD-ACTION-STATUS header in the response.
            // The value of the header will be displayed to the user.
            if (!result.Sender.ToLower().EndsWith(SenderEmailDomain))
            {
                HttpResponseMessage errorResponse = request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError());
                errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed.");
                return(errorResponse);
            }

            // prepare the response
            HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);

            response.Headers.Add("CARD-ACTION-STATUS", "Comment recorded...");

            // Further business logic code here to process the support ticket.
            #region Business logic code here to process the support ticket.
            List <Models.Comment> comments = new List <Models.Comment>();

            string newComment = cardResponse.Comment;

            if (cardResponse.CachedComments != null)
            {
                JArray cachedComments = (JArray)cardResponse.CachedComments;
                comments.AddRange(cachedComments.ToObject <List <Models.Comment> >());
            }

            // add this comment
            comments.Add(new Models.Comment()
            {
                ActionPerformer = result.ActionPerformer, CommentDate = DateTime.Now, CommentText = newComment
            });

            // create the card
            AdaptiveCards.AdaptiveCard refreshCard = CreateRefreshCard(comments);
            if (refreshCard != null)
            {
                // add the Action.Http block to the card.
                refreshCard.Actions.Add(CreateHttpAction(comments));
                response.Headers.Add("CARD-UPDATE-IN-BODY", "true");

                response.Content = new StringContent(refreshCard.ToJson());
            }
            #endregion

            return(response);
        }
 public ActionableMessageValidationResultProvider(ActionableMessageTokenValidationResult value)
 {
     _value = value;
 }
        public HttpStatusCode validateSecurity(HttpRequestMessage request, TraceWriter log)
        {
            log.Info($"Started Token Validation");
            // Validate that we have a bearer token.
            if (request.Headers.Authorization == null ||
                !string.Equals(request.Headers.Authorization.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
            {
                //return request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError());
                return(HttpStatusCode.Unauthorized);
            }

            // Get the token from the Authorization header
            string bearerToken = request.Headers.Authorization.Parameter;
            ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator();
            // This will validate that the token has been issued by Microsoft for the
            // specified target URL i.e. the target matches the intended audience (“aud” claim in token)
            //
            // In your code, replace https://api.contoso.com with your service’s base URL.
            // For example, if the service target URL is https://api.xyz.com/finance/expense?id=1234,
            // then replace https://api.contoso.com with https://api.xyz.com

            var task = Task.Run(async() => await validator.ValidateTokenAsync(bearerToken, ServiceBase));

            task.Wait();
            if (task.Result != null)
            {
                ActionableMessageTokenValidationResult result = task.Result;

                if (!result.ValidationSucceeded)
                {
                    if (result.Exception != null)
                    {
                        Trace.TraceError(result.Exception.ToString());
                    }

                    //return request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError());
                    return(HttpStatusCode.Unauthorized);
                }


                // We have a valid token. We will now verify that the sender and action performer are who
                // we expect. The sender is the identity of the entity that initially sent the Actionable
                // Message, and the action performer is the identity of the user who actually
                // took the action (“sub” claim in token).
                //
                // You should replace the code below with your own validation logic
                // In this example, we verify that the email is sent by [email protected] (expected sender)
                // and the email of the person who performed the action is [email protected] (expected recipient)

                var _username = Environment.GetEnvironmentVariable("AMUser");

                if (!string.Equals(result.Sender, _username, StringComparison.OrdinalIgnoreCase) ||
                    !string.Equals(result.ActionPerformer.Split('@')[1], _username.Split('@')[1], StringComparison.OrdinalIgnoreCase))
                {
                    return(HttpStatusCode.Forbidden);
                }

                return(HttpStatusCode.OK);
            }

            return(HttpStatusCode.InternalServerError);
        }
        public async Task <IActionResult> Post([FromBody] dynamic value, [FromHeader] string authorization)
        {
            var tunnel = await _ngrok.GetTunnelsAsync();

            var actionUrl = tunnel.Where(i => i.Proto == "https").Select(i => i.PublicUrl).FirstOrDefault();

            // Authentication
            if (!AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
            {
                return(Unauthorized("Please authenticate"));
            }

            var scheme    = headerValue.Scheme;
            var parameter = headerValue.Parameter;


            if (!string.Equals(scheme, "bearer", StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(parameter))
            {
                return(Unauthorized("Incorrect authentication scheme"));
            }

            var validator = new ActionableMessageTokenValidator();

            ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(parameter, actionUrl);

            if (!result.ValidationSucceeded)
            {
                if (result.Exception != null)
                {
                    _logger.LogError(result.Exception.ToString());
                }

                return(Unauthorized("Invalid authorization code"));
            }

            var allowListDomain = new List <string> {
                "onmicrosoft.com", "libinuko.com"
            };

            if (allowListDomain.Where(i => result.Sender.EndsWith(i, StringComparison.InvariantCultureIgnoreCase)).Count() <= 0)
            {
                Response.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed.");
                return(Forbid("Invalid sender"));
            }

            // Process valid request
            JObject jObject          = JObject.Parse(value.ToString());
            var     feedbackResponse = jObject.ToObject <FeedbackModel>();

            // Fake feedback
            string fakeFeedbackPath = Path.Combine(_env.ContentRootPath, "assets", "fake-feedback.json");
            var    fakeFeedback     = JsonSerializer.Deserialize <List <FeedbackItem> >(
                await System.IO.File.ReadAllTextAsync(fakeFeedbackPath, Encoding.UTF8)
                );

            fakeFeedback.Add(new FeedbackItem
            {
                name    = result.Sender,
                comment = feedbackResponse.Comment,
                rating  = feedbackResponse.Rating
            });

            await System.IO.File.WriteAllTextAsync(fakeFeedbackPath, JsonSerializer.Serialize(fakeFeedback));

            var feedbackSummary = new
            {
                average_rating  = fakeFeedback.Average(i => i.rating),
                feedback        = fakeFeedback,
                total_responses = fakeFeedback.Count()
            };

            // Response
            string cardJsonPath = Path.Combine(_env.ContentRootPath, "assets", "response-card.json");
            string cardJson     = await System.IO.File.ReadAllTextAsync(cardJsonPath, Encoding.UTF8);

            var cardTemplate = new AdaptiveCardTemplate(cardJson);
            var card         = cardTemplate.Expand(feedbackSummary);


            Response.Headers.Add("CARD-ACTION-STATUS", "The M365 Developer Bootcamp was received.");
            Response.Headers.Add("CARD-UPDATE-IN-BODY", "true");
            return(Ok(card));
        }
        /// <inheritdoc />
        public async Task <SurveyResponse> PostSurveyAsync(SurveyRequest request)
        {
            WebOperationContext context  = WebOperationContext.Current;
            SurveyResponse      response = new Models.SurveyResponse();

            // Validate that we have a bearer token.
            string authorization = context.IncomingRequest.Headers["authorization"];

            if (string.IsNullOrEmpty(authorization))
            {
                response.IsError = true;
                response.Message = "Bearer token not found.";
                context.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
                return(response);
            }

            string[] parts = authorization.Split(' ');
            if (parts.Length != 2 ||
                !string.Equals(parts[0], BearerTokenType, StringComparison.OrdinalIgnoreCase) ||
                string.IsNullOrEmpty(parts[1]))
            {
                response.IsError = true;
                response.Message = "Bearer token not found.";
                context.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
                return(response);
            }

            string bearerToken = parts[1];
            ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator();

            // ValidateTokenAsync will verify the following
            // 1. The token is issued by Microsoft and its digital signature is valid.
            // 2. The token has not expired.
            // 3. The audience claim matches the service domain URL.
            //
            // Replace https://api.contoso.com with your service domain URL.
            // For example, if the service URL is https://api.xyz.com/finance/expense?id=1234,
            // then replace https://api.contoso.com with https://api.xyz.com.
            ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, "https://api.contoso.com");

            if (!result.ValidationSucceeded)
            {
                if (result.Exception != null)
                {
                    Trace.TraceError(result.Exception.ToString());
                    response.Message = result.Exception.Message;
                }

                response.IsError = true;
                context.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
                return(response);
            }

            // We have a valid token. We will verify the sender and the action performer.
            // You should replace the code below with your own validation logic.
            // In this example, we verify that the email is sent by [email protected]
            // and the action performer has to be someone with @contoso.com email.
            //
            // You should also return the CARD-ACTION-STATUS header in the response.
            // The value of the header will be displayed to the user.
            if (!string.Equals(result.Sender, @"*****@*****.**", StringComparison.OrdinalIgnoreCase) ||
                !result.ActionPerformer.EndsWith("@contoso.com"))
            {
                response.IsError = true;
                response.Message = "Invalid sender or the action performer is not allowed.";
                context.OutgoingResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed.");
                context.OutgoingResponse.StatusCode = HttpStatusCode.Forbidden;
                return(response);
            }

            // Further business logic code here to process the expense report.

            context.OutgoingResponse.Headers.Add("CARD-ACTION-STATUS", "The survey was accepted.");
            return(response);
        }