/// <summary>
        /// Creates an OAuth client for the bot.
        /// </summary>
        /// <param name="turnContext">The context object for the current turn.</param>
        /// <returns>An OAuth client for the bot.</returns>
        protected async Task <OAuthClient> CreateOAuthApiClientAsync(ITurnContext turnContext)
        {
            var client = turnContext.TurnState.Get <IConnectorClient>() as ConnectorClient;

            if (client == null)
            {
                throw new ArgumentNullException("CreateOAuthApiClient: OAuth requires a valid ConnectorClient instance");
            }

            if (!OAuthClient.EmulateOAuthCards &&
                string.Equals(turnContext.Activity.ChannelId, "emulator", StringComparison.InvariantCultureIgnoreCase) &&
                (await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false)))
            {
                OAuthClient.EmulateOAuthCards = true;
            }

            if (OAuthClient.EmulateOAuthCards)
            {
                var oauthClient = new OAuthClient(client, turnContext.Activity.ServiceUrl);

                // do not await task - we want this to run in the background
                var task = Task.Run(() => oauthClient.SendEmulateOAuthCardsAsync(OAuthClient.EmulateOAuthCards));
                return(oauthClient);
            }

            return(new OAuthClient(client, OAuthClient.OAuthEndpoint));
        }
        /// <summary>
        /// Authenticates the request and adds the activity's <see cref="Activity.ServiceUrl"/>
        /// to the set of trusted URLs.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <param name="authHeader">The authentication header.</param>
        /// <param name="credentials">The bot's credential provider.</param>
        /// <param name="authConfig">The optional authentication configuration.</param>
        /// <param name="httpClient">The HTTP client.</param>
        /// <returns>A task that represents the work queued to execute.</returns>
        /// <remarks>If the task completes successfully, the result contains the claims-based
        /// identity for the request.</remarks>
        public static async Task <ClaimsIdentity> AuthenticateRequest(IActivity activity, string authHeader, ICredentialProvider credentials, AuthenticationConfiguration authConfig, HttpClient httpClient = null)
        {
            if (authConfig == null)
            {
                throw new ArgumentNullException(nameof(authConfig));
            }

            if (string.IsNullOrWhiteSpace(authHeader))
            {
                var isAuthDisabled = await credentials.IsAuthenticationDisabledAsync().ConfigureAwait(false);

                if (isAuthDisabled)
                {
                    // In the scenario where Auth is disabled, we still want to have the
                    // IsAuthenticated flag set in the ClaimsIdentity. To do this requires
                    // adding in an empty claim.
                    return(new ClaimsIdentity(new List <Claim>(), "anonymous"));
                }

                // No Auth Header. Auth is required. Request is not authorized.
                throw new UnauthorizedAccessException();
            }

            var claimsIdentity = await ValidateAuthHeader(authHeader, credentials, activity.ChannelId, authConfig, activity.ServiceUrl, httpClient ?? _httpClient).ConfigureAwait(false);

            MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);

            return(claimsIdentity);
        }
Beispiel #3
0
        /// <summary>
        /// Creates an OAuth client for the bot.
        /// </summary>
        /// <param name="turnContext">The context object for the current turn.</param>
        /// <returns>An OAuth client for the bot.</returns>
        protected virtual async Task <OAuthClient> CreateOAuthApiClientAsync(ITurnContext turnContext)
        {
            if (!OAuthClientConfig.EmulateOAuthCards &&
                string.Equals(turnContext.Activity.ChannelId, "emulator", StringComparison.InvariantCultureIgnoreCase) &&
                (await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false)))
            {
                OAuthClientConfig.EmulateOAuthCards = true;
            }

            var connectorClient = turnContext.TurnState.Get <IConnectorClient>();

            if (connectorClient == null)
            {
                throw new InvalidOperationException("An IConnectorClient is required in TurnState for this operation.");
            }

            if (OAuthClientConfig.EmulateOAuthCards)
            {
                // do not await task - we want this to run in the background
                var oauthClient = new OAuthClient(new Uri(turnContext.Activity.ServiceUrl), connectorClient.Credentials);
                var task        = Task.Run(() => OAuthClientConfig.SendEmulateOAuthCardsAsync(oauthClient, OAuthClientConfig.EmulateOAuthCards));
                return(oauthClient);
            }

            return(new OAuthClient(new Uri(OAuthClientConfig.OAuthEndpoint), connectorClient.Credentials));
        }
Beispiel #4
0
        public static async Task <ClaimsIdentity> ValidateAuthHeader(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient = null)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                bool isAuthDisabled = await credentials.IsAuthenticationDisabledAsync();

                if (isAuthDisabled)
                {
                    // In the scenario where Auth is disabled, we still want to have the
                    // IsAuthenticated flag set in the ClaimsIdentity. To do this requires
                    // adding in an empty claim.
                    ClaimsIdentity anonymousAuthenticatedIdentity = new ClaimsIdentity(new List <Claim>(), "anonymous");
                    return(anonymousAuthenticatedIdentity);
                }

                // No Auth Header. Auth is required. Request is not authorized.
                throw new UnauthorizedAccessException();
            }

            bool usingEmulator = EmulatorValidation.IsTokenFromEmulator(authHeader);

            if (usingEmulator)
            {
                return(await EmulatorValidation.AuthenticateEmulatorToken(authHeader, credentials, httpClient ?? _httpClient));
            }
            else
            {
                return(await ChannelValidation.AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient ?? _httpClient));
            }
        }
 protected async Task <bool> TrySetEmulatingOAuthCards(ITurnContext turnContext)
 {
     if (!_isEmulatingOAuthCards &&
         string.Equals(turnContext.Activity.ChannelId, "emulator", StringComparison.InvariantCultureIgnoreCase) &&
         (await _credentialProvider.IsAuthenticationDisabledAsync()))
     {
         _isEmulatingOAuthCards = true;
     }
     return(_isEmulatingOAuthCards);
 }
 public async Task SkillPingAsync()
 {
     if (_credentialProvider != null && !await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false))
     {
         await _authenticator.AuthenticateAsync(Request, Response).ConfigureAwait(false);
     }
     else
     {
         Response.StatusCode = (int)HttpStatusCode.OK;
     }
 }
Beispiel #7
0
        public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            if (!await Options.IsAuthenticationDisabledAsync())
            {
                var activities = GetActivities(context);

                foreach (var activity in activities)
                {
                    MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
                }
            }
            await next();
        }
Beispiel #8
0
        /// <summary>
        /// Validates the security tokens required by the Bot Framework Protocol. Throws on any exceptions.
        /// </summary>
        /// <param name="activity">The incoming Activity from the Bot Framework or the Emulator</param>
        /// <param name="authHeader">The Bearer token included as part of the request</param>
        /// <param name="credentials">The set of valid credentials, such as the Bot Application ID</param>
        /// <param name="httpClient">Validating an Activity requires validating the claimset on the security token. This
        /// validation may require outbound calls for Endorsement validation and other checks. Those calls are made to
        /// TLS services, which are (latency wise) expensive resources. The httpClient passed in here, if shared by the layers
        /// above from call to call, enables connection reuse which is a significant performance and resource improvement.</param>
        /// <returns>Task tracking operation</returns>
        public static async Task AssertValidActivity(IActivity activity, string authHeader, ICredentialProvider credentials, HttpClient httpClient = null)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                // No auth header was sent. We might be on the anonymous code path.
                bool isAuthDisabled = await credentials.IsAuthenticationDisabledAsync();

                if (isAuthDisabled)
                {
                    // We are on the anonymous code path.
                    return;
                }
            }

            // Go through the standard authentication path.
            await JwtTokenValidation.AuthenticateRequest(activity, authHeader, credentials, httpClient ?? _httpClient);
        }
        /// <summary>
        /// Validates the security tokens required by the Bot Framework Protocol. Throws on any exceptions. 
        /// </summary>
        /// <param name="activity">The incoming Activity from the Bot Framework or the Emulator</param>
        /// <param name="authHeader">The Bearer token included as part of the request</param>
        /// <param name="credentials">The set of valid credentials, such as the Bot Application ID</param>
        /// <param name="httpClient">Validating an Activity requires validating the claimset on the security token. This 
        /// validation may require outbound calls for Endorsement validation and other checks. Those calls are made to
        /// TLS services, which are (latency wise) expensive resources. The httpClient passed in here, if shared by the layers
        /// above from call to call, enables connection reuse which is a significant performance and resource improvement.</param>
        /// <returns>Nothing</returns>
        public static async Task AssertValidActivity(Activity activity, string authHeader, ICredentialProvider credentials, HttpClient httpClient)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                // No auth header was sent. We might be on the anonymous code path. 
                bool isAuthDisabled = await credentials.IsAuthenticationDisabledAsync();
                if (isAuthDisabled)
                {
                    // We are on the anonymous code path. 
                    return;
                }
            }

            // Go through the standard authentication path. 
            await JwtTokenValidation.ValidateAuthHeader(authHeader, credentials, activity.ServiceUrl, httpClient);

            // On the standard Auth path, we need to trust the URL that was incoming. 
            MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
        }
Beispiel #10
0
        private async Task <ClaimsIdentity> AuthenticateAsync(string authHeader)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                var isAuthDisabled = await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false);

                if (isAuthDisabled)
                {
                    // In the scenario where auth is disabled, we still want to have the
                    // IsAuthenticated flag set in the ClaimsIdentity. To do this requires
                    // adding in an empty claim.
                    return(new ClaimsIdentity(new List <Claim>(), "anonymous"));
                }

                // No auth header. Auth is required. Request is not authorized.
                throw new UnauthorizedAccessException();
            }

            return(await JwtTokenValidation.ValidateAuthHeader(authHeader, _credentialProvider, _channelProvider, "unknown", _authConfiguration).ConfigureAwait(false));
        }
        /// <summary>
        /// Helper to authenticate the header.
        /// </summary>
        /// <remarks>
        /// This code is very similar to the code in <see cref="JwtTokenValidation.AuthenticateRequest(IActivity, string, ICredentialProvider, IChannelProvider, AuthenticationConfiguration, HttpClient)"/>,
        /// we should move this code somewhere in that library when we refactor auth, for now we keep it private to avoid adding more public static
        /// functions that we will need to deprecate later.
        /// </remarks>
        /// <param name="authHeader">The auth header containing JWT token.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>A <see cref="ClaimsIdentity"/> representing the claims associated with given header.</returns>
        internal override async Task <ClaimsIdentity> AuthenticateAsync(string authHeader, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                var isAuthDisabled = await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false);

                if (!isAuthDisabled)
                {
                    // No auth header. Auth is required. Request is not authorized.
                    throw new UnauthorizedAccessException();
                }

                // In the scenario where auth is disabled, we still want to have the
                // IsAuthenticated flag set in the ClaimsIdentity.
                // To do this requires adding in an empty claim.
                // Since ChannelServiceHandler calls are always a skill callback call, we set the skill claim too.
                return(SkillValidation.CreateAnonymousSkillClaim());
            }

            // Validate the header and extract claims.
            return(await JwtTokenValidation.ValidateAuthHeader(authHeader, _credentialProvider, ChannelProvider, "unknown", _authConfiguration).ConfigureAwait(false));
        }
Beispiel #12
0
        public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (httpRequest == null)
            {
                throw new ArgumentNullException(nameof(httpRequest));
            }

            if (httpResponse == null)
            {
                throw new ArgumentNullException(nameof(httpResponse));
            }

            if (bot == null)
            {
                throw new ArgumentNullException(nameof(bot));
            }

            if (!httpRequest.HttpContext.WebSockets.IsWebSocketRequest)
            {
                httpResponse.StatusCode = (int)HttpStatusCode.BadRequest;
                await httpResponse.WriteAsync("Upgrade to WebSocket required.").ConfigureAwait(false);

                return;
            }

            ClaimsIdentity claims;

            // only perform auth when it's enabled
            if (_credentialProvider != null && !await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false))
            {
                claims = await _authenticator.AuthenticateAsync(httpRequest, httpResponse).ConfigureAwait(false);
            }
            else
            {
                claims = new ClaimsIdentity(new List <Claim>(), "anonymous");
            }

            await CreateWebSocketConnectionAsync(claims, httpRequest.HttpContext, bot).ConfigureAwait(false);
        }
        /// <summary>
        /// Authenticates the request and adds the activity's <see cref="Activity.ServiceUrl"/>
        /// to the set of trusted URLs.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <param name="authHeader">The authentication header.</param>
        /// <param name="credentials">The bot's credential provider.</param>
        /// <param name="provider">The bot's channel service provider.</param>
        /// <param name="authConfig">The optional authentication configuration.</param>
        /// <param name="httpClient">The HTTP client.</param>
        /// <returns>A task that represents the work queued to execute.</returns>
        /// <remarks>If the task completes successfully, the result contains the claims-based
        /// identity for the request.</remarks>
#pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat)
        public static async Task <ClaimsIdentity> AuthenticateRequest(IActivity activity, string authHeader, ICredentialProvider credentials, IChannelProvider provider, AuthenticationConfiguration authConfig, HttpClient httpClient = null)
#pragma warning restore UseAsyncSuffix // Use Async suffix
        {
            if (authConfig == null)
            {
                throw new ArgumentNullException(nameof(authConfig));
            }

            if (string.IsNullOrWhiteSpace(authHeader))
            {
                var isAuthDisabled = await credentials.IsAuthenticationDisabledAsync().ConfigureAwait(false);

                if (!isAuthDisabled)
                {
                    // No Auth Header and Auth is required. Request is not authorized.
                    throw new UnauthorizedAccessException();
                }

                // Check if the activity is for a skill call and is coming from the Emulator.
                if (activity.ChannelId == Channels.Emulator && activity.Recipient?.Role == RoleTypes.Skill)
                {
                    // Return an anonymous claim with an anonymous skill AppId
                    return(SkillValidation.CreateAnonymousSkillClaim());
                }

                // In the scenario where Auth is disabled, we still want to have the
                // IsAuthenticated flag set in the ClaimsIdentity. To do this requires
                // adding in an empty claim.
                return(new ClaimsIdentity(new List <Claim>(), AuthenticationConstants.AnonymousAuthType));
            }

            // Validate the header and extract claims.
            var claimsIdentity = await ValidateAuthHeader(authHeader, credentials, provider, activity.ChannelId, authConfig, activity.ServiceUrl, httpClient ?? _httpClient).ConfigureAwait(false);

            AppCredentials.TrustServiceUrl(activity.ServiceUrl);
            return(claimsIdentity);
        }
        /// <summary>
        /// Process the initial request to establish a long lived connection via a streaming server.
        /// </summary>
        /// <param name="onTurnError"> The function to execute on turn errors.</param>
        /// <param name="middlewareSet">The set of middleware to perform on each turn.</param>
        /// <param name="httpRequest">The connection request.</param>
        /// <param name="httpResponse">The response sent on error or connection termination.</param>
        /// <param name="cancellationToken">Optional cancellation token.</param>
        /// <returns>Returns on task completion.</returns>
        internal async Task ProcessAsync(Func <ITurnContext, Exception, Task> onTurnError, List <Builder.IMiddleware> middlewareSet, HttpRequest httpRequest, HttpResponse httpResponse, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (httpRequest == null)
            {
                throw new ArgumentNullException(nameof(httpRequest));
            }

            if (httpResponse == null)
            {
                throw new ArgumentNullException(nameof(httpResponse));
            }

            if (!httpRequest.HttpContext.WebSockets.IsWebSocketRequest)
            {
                httpRequest.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                await httpRequest.HttpContext.Response.WriteAsync("Upgrade to WebSocket is required.").ConfigureAwait(false);

                return;
            }

            try
            {
                if (!await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false))
                {
                    var authHeader = httpRequest.Headers.Where(x => x.Key.ToLower() == AuthHeaderName).FirstOrDefault().Value.FirstOrDefault();
                    var channelId  = httpRequest.Headers.Where(x => x.Key.ToLower() == ChannelIdHeaderName).FirstOrDefault().Value.FirstOrDefault();

                    if (string.IsNullOrWhiteSpace(authHeader))
                    {
                        await MissingAuthHeaderHelperAsync(AuthHeaderName, httpRequest).ConfigureAwait(false);

                        return;
                    }

                    if (string.IsNullOrWhiteSpace(channelId))
                    {
                        await MissingAuthHeaderHelperAsync(ChannelIdHeaderName, httpRequest).ConfigureAwait(false);

                        return;
                    }

                    var claimsIdentity = await JwtTokenValidation.ValidateAuthHeader(authHeader, _credentialProvider, _channelProvider, channelId).ConfigureAwait(false);

                    if (!claimsIdentity.IsAuthenticated)
                    {
                        httpRequest.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;

                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                httpRequest.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                await httpRequest.HttpContext.Response.WriteAsync("Error while attempting to authorize connection.").ConfigureAwait(false);

                throw ex;
            }

            await CreateStreamingServerConnectionAsync(onTurnError, middlewareSet, httpRequest.HttpContext).ConfigureAwait(false);
        }