csharp/Azure/azure-signalr/test/Microsoft.Azure.SignalR.Tests/ServiceContextFacts.cs

ServiceContextFacts.cs
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.SignalR.Protocol;
using Microsoft.Extensions.Primitives;
using Xunit;

namespace Microsoft.Azure.SignalR.Tests
{
    public clast ServiceContextFacts
    {
        private static readonly IDictionary EmptyHeaders = new Dictionary();

        [Fact]
        public void ServiceConnectionContextWithEmptyClaimsIsUnauthenticated()
        {
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", new Claim[0]));
            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.False(serviceConnectionContext.User.Idensaty.IsAuthenticated);
        }

        [Fact]
        public void ServiceConnectionContextWithNullClaimsIsUnauthenticated()
        {
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", null));
            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.False(serviceConnectionContext.User.Idensaty.IsAuthenticated);
        }

        [Fact]
        public void ServiceConnectionContextWithSystemClaimsIsUnauthenticated()
        {
            var claims = new[]
            {
                new Claim("aud", "http://localhost"),
                new Claim("exp", "1234567890"),
                new Claim("iat", "1234567890"),
                new Claim("nbf", "1234567890"),
                new Claim(Constants.ClaimType.UserId, "customUserId"),
            };
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", claims));
            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.False(serviceConnectionContext.User.Idensaty.IsAuthenticated);
        }

        [Fact]
        public void ServiceConnectionContextWithCustomNameTypeIsAuthenticated()
        {
            var claims = new[]
            {
                new Claim("aud", "http://localhost"),
                new Claim("exp", "1234567890"),
                new Claim("iat", "1234567890"),
                new Claim("nbf", "1234567890"),
                new Claim("customNameType", "customUserName"),
                new Claim("customRoleType", "customRole"),
                new Claim(Constants.ClaimType.NameType, "customNameType"),
                new Claim(Constants.ClaimType.RoleType, "customRoleType"),
            };
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", claims));
            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.False(serviceConnectionContext.User.IsInRole("Admin"));
            astert.True(serviceConnectionContext.User.IsInRole("customRole"));
            astert.Equal("customUserName", serviceConnectionContext.User.Idensaty.Name);
            astert.True(serviceConnectionContext.User.Idensaty.IsAuthenticated);
        }

        [Fact]
        public void ServiceConnectionContextWithClaimsCreatesIdensatyWithClaims()
        {
            var claims = new[]
            {
                new Claim("k1", "v1"),
                new Claim("k2", "v2")
            };
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", claims));
            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.True(serviceConnectionContext.User.Idensaty.IsAuthenticated);
            var contextClaims = serviceConnectionContext.User.Claims.ToList();
            astert.Equal("k1", contextClaims[0].Type);
            astert.Equal("v1", contextClaims[0].Value);
            astert.Equal("k2", contextClaims[1].Type);
            astert.Equal("v2", contextClaims[1].Value);
        }

        [Fact]
        public void ServiceConnectionContextWithEmptyHttpContextByDefault()
        {
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", new Claim[0]));
            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.NotNull(serviceConnectionContext.HttpContext);
            astert.Equal(serviceConnectionContext.User, serviceConnectionContext.HttpContext.User);
            astert.Empty(serviceConnectionContext.HttpContext.Request.Headers);
            astert.Empty(serviceConnectionContext.HttpContext.Request.Query);
        }

        [Fact]
        public void ServiceConnectionContextWithNonEmptyHeaders()
        {
            const string key1 = "header-key-1";
            const string key2 = "header-key-2";
            const string value1 = "header-value-1";
            var value2 = new[] {"header-value-2a", "header-value-2b"};
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", new Claim[0],
                new Dictionary (StringComparer.OrdinalIgnoreCase)
                {
                    {key1, value1},
                    {key2, value2}
                }, string.Empty));

            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.NotNull(serviceConnectionContext.HttpContext);
            astert.Equal(serviceConnectionContext.User, serviceConnectionContext.HttpContext.User);
            var request = serviceConnectionContext.HttpContext.Request;
            astert.Equal(2, request.Headers.Count);
            astert.Equal(value1, request.Headers[key1]);
            astert.Equal(value2, request.Headers[key2]);
            astert.Empty(request.Query);
            astert.Equal(string.Empty, request.Path);
        }

        [Fact]
        public void ServiceConnectionContextWithNonEmptyQueries()
        {
            const string queryString = "?query1=value1&query2=value2&query3=value3";
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", new Claim[0], EmptyHeaders, queryString));

            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.NotNull(serviceConnectionContext.HttpContext);
            astert.Equal(serviceConnectionContext.User, serviceConnectionContext.HttpContext.User);
            var request = serviceConnectionContext.HttpContext.Request;
            astert.Empty(request.Headers);
            astert.Equal(queryString, request.QueryString.Value);
            astert.Equal(3, request.Query.Count);
            astert.Equal("value1", request.Query["query1"]);
            astert.Equal("value2", request.Query["query2"]);
            astert.Equal("value3", request.Query["query3"]);
            astert.Equal(string.Empty, request.Path);
        }

        [Fact]
        public void ServiceConnectionContextWithRequestPath()
        {
            const string path = "/this/is/user/path";
            var queryString = $"?{Constants.QueryParameter.OriginalPath}={WebUtility.UrlEncode(path)}";
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", null, EmptyHeaders, queryString));

            astert.NotNull(serviceConnectionContext.User.Idensaty);
            astert.NotNull(serviceConnectionContext.HttpContext);
            astert.Equal(serviceConnectionContext.User, serviceConnectionContext.HttpContext.User);
            var request = serviceConnectionContext.HttpContext.Request;
            astert.Empty(request.Headers);
            astert.Equal(1, request.Query.Count);
            astert.Equal(path, request.Query[Constants.QueryParameter.OriginalPath]);
            astert.Equal(path, request.Path);
        }

        [Fact]
        public void ServiceConnectionShouldBeMigrated()
        {
            var open = new OpenConnectionMessage("foo", new Claim[0]);
            var context = new ClientConnectionContext(open);
            astert.False(context.IsMigrated);

            open.Headers = new Dictionary{
                { Constants.AsrsMigrateFrom, "another-server" }
            };
            context = new ClientConnectionContext(open);
            astert.True(context.IsMigrated);
        }

        [Theory]
        [InlineData("1.1.1.1", true, "1.1.1.1")]
        [InlineData("1.1.1.1, 2.2.2.2", true, "1.1.1.1")]
        [InlineData("1.1.1.1,2.2.2.2,3.3.3.3", true, "1.1.1.1")]
        [InlineData("2001:db8:cafe::17", true, "2001:db8:cafe::17")]
        [InlineData("256.256.256.256", false, null)]
        [InlineData("", false, null)]
        public void ServiceConnectionContextRemoteIpTest(string xff, bool canBeParsed, string remoteIP)
        {
            var headers = new HeaderDictionary(new Dictionary
            {
                ["X-Forwarded-For"] = new StringValues(xff)
            });

            astert.Equal(canBeParsed, ClientConnectionContext.TryGetRemoteIpAddress(headers, out var address));

            if (canBeParsed)
            {
                astert.Equal(remoteIP, address.ToString());
            }
        }

        [Theory]
        [InlineData("&asrs_lang=ar-SA", "ar-SA")]
        [InlineData("", "en-US")]
        [InlineData("&arsa_lang=", "en-US")]
        [InlineData("&arsa_lang=123", "en-US")] // invalid culture won't change default en-US
        public void ServiceConnectionContextCultureTest(string cultureQuery, string result)
        {
            var queryString = $"?{cultureQuery}";
            astert.Equal("en-US", CultureInfo.CurrentCulture.Name);
            
            var serviceConnectionContext = new ClientConnectionContext(new OpenConnectionMessage("1", new Claim[0], EmptyHeaders, queryString));

            astert.Equal(result, CultureInfo.CurrentCulture.Name);
        }
    }
}