public async Task <TAuthorizationToken> GetAsync <TAuthorizationToken>(DeploymentScope deploymentScope)
        where TAuthorizationToken : AuthorizationToken, new()
    {
        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        var client = await tableClient.ConfigureAwait(false);

        var rowKey       = AuthorizationEntity.GetEntityId(deploymentScope);
        var partitionKey = string.Join(",", typeof(TAuthorizationToken).AssemblyQualifiedName.Split(',').Take(2));

        try
        {
            var response = await client
                           .GetEntityAsync <TAuthorizationToken>(partitionKey, rowKey)
                           .ConfigureAwait(false);

            return(response.Value);
        }
        catch (RequestFailedException ex) when(ex.Status == 404)
        {
            // doesn't exist
            return(null);
        }
    }
Exemple #2
0
    public static string GetEntityId(DeploymentScope deploymentScope)
    {
        var entityId = Guid.Empty;

        if (deploymentScope is not null)
        {
            var result = Merge(Guid.Parse(deploymentScope.Organization), Guid.Parse(deploymentScope.Id));

            entityId = new Guid(result.ToArray());
        }

        return(entityId.ToString());
Exemple #3
0
    async Task <AzureServicePrincipal> IAdapterAuthorize.ResolvePrincipalAsync(DeploymentScope deploymentScope, HttpRequest request)
    {
        const string SignatureHeader = "X-Hub-Signature-256";

        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        if (request is null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (request.Headers.TryGetValue(SignatureHeader, out var signatureValue) && !string.IsNullOrEmpty(signatureValue))
        {
            var token = await TokenClient
                        .GetAsync <GitHubToken>(deploymentScope)
                        .ConfigureAwait(false);

            if (!string.IsNullOrEmpty(token?.WebhookSecret))
            {
                var signature = signatureValue
                                .ToString()
                                .Substring(signatureValue.ToString().IndexOf('=') + 1);

                // CAUTION - we need to read the body but leave
                // the request's body stream open so it stays
                // available for subsequent requests

                var body = await request
                           .ReadStringAsync(leaveOpen : true)
                           .ConfigureAwait(false);

                var secret = Encoding.ASCII
                             .GetBytes(token.WebhookSecret);

                using var hmac = new HMACSHA256(secret);

                var hash = hmac
                           .ComputeHash(Encoding.ASCII.GetBytes(body))
                           .ToHexString();

                if (hash.Equals(signature))
                {
                    // signature successfully validated - lets use the adapters identity to proceed
                    return(await GetServiceIdentityAsync(deploymentScope).ConfigureAwait(false));
                }
            }
        }

        return(null);
    }
Exemple #4
0
    public override async Task <bool> IsAuthorizedAsync(DeploymentScope deploymentScope)
    {
        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        var token = await TokenClient
                    .GetAsync <GitHubToken>(deploymentScope)
                    .ConfigureAwait(false);

        return(!(token is null));
    }
Exemple #5
0
    private async Task <GitHubClient> CreateClientAsync(DeploymentScope deploymentScope, string acceptHeader = null)
    {
        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        var token = await TokenClient
                    .GetAsync <GitHubToken>(deploymentScope)
                    .ConfigureAwait(false);

        return(await CreateClientAsync(token, acceptHeader)
               .ConfigureAwait(false));
    }
 public AzureDevOpsToken(DeploymentScope deployementScope) : base(GetEntityId(deployementScope))
 {
 }
Exemple #7
0
 public GitHubToken(DeploymentScope deployementScope) : base(GetEntityId(deployementScope))
 {
 }
Exemple #8
0
 public Task <IAuthorizationEndpoints> GetAuthorizationEndpointsAsync(DeploymentScope deploymentScope)
 => deploymentScope is null ? throw new ArgumentNullException(nameof(deploymentScope)) : Task.FromResult <IAuthorizationEndpoints>(new AuthorizationEndpoints()
Exemple #9
0
 protected override Task <Component> CreateComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue)
 => ExecuteAsync(component, contextUser, commandQueue, async(client, ownerLogin, project, memberTeam, adminTeam) =>
Exemple #10
0
    async Task <IActionResult> IAdapterAuthorize.HandleCallbackAsync(DeploymentScope deploymentScope, HttpRequest request, IAuthorizationEndpoints authorizationEndpoints)
    {
        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        if (request is null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (authorizationEndpoints is null)
        {
            throw new ArgumentNullException(nameof(authorizationEndpoints));
        }

        var json = await request
                   .ReadJsonAsync()
                   .ConfigureAwait(false);

        var appId = json
                    .SelectToken("installation.app_id")?
                    .ToString();

        var token = await TokenClient
                    .GetAsync <GitHubToken>(deploymentScope)
                    .ConfigureAwait(false);

        if (token?.ApplicationId?.Equals(appId, StringComparison.OrdinalIgnoreCase) ?? false)
        {
            var action = json
                         .SelectToken("action")?
                         .ToString();

            switch (action)
            {
            case "created":

                token.InstallationId = json
                                       .SelectToken("installation.id")?
                                       .ToString();

                break;

            case "deleted":

                token = new GitHubToken(deploymentScope);
                break;

            case "suspend":

                token.Suspended = true;
                break;

            case "unsuspend":

                token.Suspended = false;
                break;

            default:

                token = null;
                break;
            }

            if (token is not null)
            {
                _ = await TokenClient
                    .SetAsync(token)
                    .ConfigureAwait(false);

                return(new OkResult());
            }
        }

        return(new NotFoundResult());
    }
Exemple #11
0
 public GitHubSession(DeploymentScope deploymentScope = null) : base(GetEntityId(deploymentScope))
 {
 }
    protected override Task <Component> CreateComponentAsync(Component component, Organization organization, DeploymentScope deploymentScope, Project project, User contextUser, IAsyncCollector <ICommand> commandQueue)
    => WithKubernetesContext(component, deploymentScope, async(client, data, roleDefinition, serviceAccount) =>
    {
        var componentNamespace = new V1Namespace()
        {
            Metadata = new V1ObjectMeta()
            {
                Name = $"{data.Namespace}-{component.Id}"
            }
        };

        try
        {
            componentNamespace = await client
                                 .CreateNamespaceAsync(componentNamespace)
                                 .ConfigureAwait(false);
        }
        catch (HttpOperationException exc) when(exc.Response.StatusCode == System.Net.HttpStatusCode.Conflict)
        {
            componentNamespace = await client
                                 .ReadNamespaceAsync(componentNamespace.Metadata.Name)
                                 .ConfigureAwait(false);
        }

        var roleBinding = new V1RoleBinding()
        {
            Metadata = new V1ObjectMeta()
            {
                Name = "runner"
            },
            RoleRef = new V1RoleRef()
            {
                ApiGroup = roleDefinition.ApiGroup(),
                Kind     = roleDefinition.Kind,
                Name     = roleDefinition.Name()
            },
            Subjects = new List <V1Subject>()
            {
                new V1Subject()
                {
                    ApiGroup          = serviceAccount.ApiGroup(),
                    Kind              = serviceAccount.Kind,
                    Name              = serviceAccount.Name(),
                    NamespaceProperty = serviceAccount.Namespace()
                }
            }
        };

        try
        {
            await client
            .CreateNamespacedRoleBindingAsync(roleBinding, componentNamespace.Metadata.Name)
            .ConfigureAwait(false);
        }
        catch (HttpOperationException exc) when(exc.Response.StatusCode == System.Net.HttpStatusCode.Conflict)
        {
            await client
            .ReplaceNamespacedRoleBindingAsync(roleBinding, roleBinding.Metadata.Name, componentNamespace.Metadata.Name)
            .ConfigureAwait(false);
        }

        return(component);
    });
 private KubernetesData GetKubernetesData(DeploymentScope deploymentScope)
 => TeamCloudSerialize.DeserializeObject <KubernetesData>(deploymentScope.InputData);
    protected override async Task <Component> UpdateComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue)
    {
        if (component is null)
        {
            throw new ArgumentNullException(nameof(component));
        }

        if (AzureResourceIdentifier.TryParse(component.ResourceId, out var componentResourceId))
        {
            var tasks = new Task[] {
                UpdateComponentRoleAssignmentsAsync(),
                UpdateComponentTagsAsync()
            };

            await Task.WhenAll(tasks).ConfigureAwait(false);
        }

        return(component);

        async Task UpdateComponentRoleAssignmentsAsync()
        {
            var roleAssignmentMap = await GetRoleAssignmentsAsync(component)
                                    .ConfigureAwait(false);

            if (AzureResourceIdentifier.TryParse(component.IdentityId, out var identityResourceId))
            {
                var session = await azureResourceService.AzureSessionService
                              .CreateSessionAsync(identityResourceId.SubscriptionId)
                              .ConfigureAwait(false);

                var identity = await session.Identities
                               .GetByIdAsync(identityResourceId.ToString())
                               .ConfigureAwait(false);

                roleAssignmentMap
                .Add(identity.PrincipalId, Enumerable.Repeat(AzureRoleDefinition.Contributor, 1));
            }

            if (string.IsNullOrEmpty(componentResourceId.ResourceGroup))
            {
                var subscription = await azureResourceService
                                   .GetSubscriptionAsync(componentResourceId.SubscriptionId, throwIfNotExists : true)
                                   .ConfigureAwait(false);

                if (subscription is not null)
                {
                    await subscription.SetRoleAssignmentsAsync(roleAssignmentMap).ConfigureAwait(false);
                }
            }
            else
            {
                var resourceGroup = await azureResourceService
                                    .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup, throwIfNotExists : true)
                                    .ConfigureAwait(false);

                if (resourceGroup is not null)
                {
                    await resourceGroup.SetRoleAssignmentsAsync(roleAssignmentMap).ConfigureAwait(false);
                }
            }
        }

        async Task UpdateComponentTagsAsync()
        {
            var tenantId = await azureResourceService.AzureSessionService
                           .GetTenantIdAsync()
                           .ConfigureAwait(false);

            var organization = await organizationRepository
                               .GetAsync(tenantId.ToString(), component.Organization, true)
                               .ConfigureAwait(false);

            var project = await projectRepository
                          .GetAsync(component.Organization, component.ProjectId, true)
                          .ConfigureAwait(false);

            var tags = organization.Tags
                       .Union(project.Tags)
                       .GroupBy(kvp => kvp.Key)
                       .ToDictionary(g => g.Key, g => g.First().Value);

            if (string.IsNullOrEmpty(componentResourceId.ResourceGroup))
            {
                var subscription = await azureResourceService
                                   .GetSubscriptionAsync(componentResourceId.SubscriptionId)
                                   .ConfigureAwait(false);

                if (subscription is not null)
                {
                    await subscription.SetTagsAsync(tags, true).ConfigureAwait(false);
                }
            }
            else
            {
                var resourceGroup = await azureResourceService
                                    .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup)
                                    .ConfigureAwait(false);

                if (resourceGroup is not null)
                {
                    await resourceGroup.SetTagsAsync(tags, true).ConfigureAwait(false);
                }
            }
        }
    }
    protected override Task <NetworkCredential> GetServiceCredentialAsync(Component component, Organization organization, DeploymentScope deploymentScope, Project project)
    => WithKubernetesContext(component, deploymentScope, async(client, data, roleDefinition, serviceAccount) =>
    {
        var configuration = await client
                            .CreateClusterConfigAsync(serviceAccount)
                            .ConfigureAwait(false);

        return(new NetworkCredential()
        {
            Domain = client.BaseUri.ToString(),
            Password = new SerializerBuilder().Build().Serialize(configuration),
            UserName = serviceAccount.Name()
        });
    });
    protected override Task <Component> UpdateComponentAsync(Component component, Organization organization, DeploymentScope deploymentScope, Project project, User contextUser, IAsyncCollector <ICommand> commandQueue)
    => WithKubernetesContext(component, deploymentScope, (client, data, roleDefinition, serviceAccount) =>
    {
        //TODO: implement some update logic - e.g. permission management for project users

        return(Task.FromResult(component));
    });
    protected override Task <Component> DeleteComponentAsync(Component component, Organization organization, DeploymentScope deploymentScope, Project project, User contextUser, IAsyncCollector <ICommand> commandQueue)
    => WithKubernetesContext(component, deploymentScope, async(client, data, roleDefinition, serviceAccount) =>
    {
        try
        {
            await client
            .DeleteNamespaceAsync($"{data.Namespace}-{component.Id}")
            .ConfigureAwait(false);
        }
        catch (HttpOperationException exc) when(exc.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            // swallow - the namespace was already deleted
        }

        return(component);
    });
Exemple #18
0
 public AzureDevOpsSession(DeploymentScope deploymentScope) : base(GetEntityId(deploymentScope))
 {
 }
Exemple #19
0
    async Task <IActionResult> IAdapterAuthorize.HandleAuthorizeAsync(DeploymentScope deploymentScope, HttpRequest request, IAuthorizationEndpoints authorizationEndpoints)
    {
        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        if (request is null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (authorizationEndpoints is null)
        {
            throw new ArgumentNullException(nameof(authorizationEndpoints));
        }

        var queryParams = Url.ParseQueryParams(request.QueryString.ToString());
        var queryState  = queryParams.GetValueOrDefault("state");
        var queryCode   = queryParams.GetValueOrDefault("code");
        var queryError  = queryParams.GetValueOrDefault("error");

        var session = await SessionClient
                      .GetAsync <GitHubSession>(deploymentScope)
                      .ConfigureAwait(false);

        session ??= await SessionClient
        .SetAsync(new GitHubSession(deploymentScope))
        .ConfigureAwait(false);

        var data = string.IsNullOrWhiteSpace(deploymentScope.InputData)
            ? default
            : TeamCloudSerialize.DeserializeObject <GitHubData>(deploymentScope.InputData);

        var token = await TokenClient
                    .GetAsync <GitHubToken>(deploymentScope)
                    .ConfigureAwait(false);

        if (string.IsNullOrEmpty(queryError) && Guid.TryParse(queryState, out var stateId))
        {
            if (!stateId.ToString().Equals(session.SessionId, StringComparison.OrdinalIgnoreCase))
            {
                return(new RedirectResult(authorizationEndpoints.AuthorizationUrl.SetQueryParam("error", "Session timed out.").ToString()));
            }
            else if (string.IsNullOrWhiteSpace(queryCode))
            {
                return(new RedirectResult(authorizationEndpoints.AuthorizationUrl.SetQueryParam("error", "Missing GitHub handshake information.").ToString()));
            }

            token ??= new GitHubToken(deploymentScope);

            // Using Flurl as Octokit doesn't support this API yet
            // https://github.com/octokit/octokit.net/issues/2138

            var url = $"https://api.github.com/app-manifests/{queryCode}/conversions";

            var response = await url
                           .WithHeader("User-Agent", GitHubConstants.ProductHeader.ToString())
                           .PostStringAsync(string.Empty)
                           .ConfigureAwait(false);

            if (!response.IsSuccessStatusCode())
            {
                return(new RedirectResult(authorizationEndpoints.AuthorizationUrl.SetQueryParam("error", $"Failed to get application token ({response.StatusCode} - {response.ResponseMessage.ReasonPhrase}).").ToString()));
            }

            var json = await response
                       .GetStringAsync()
                       .ConfigureAwait(false);

            TeamCloudSerialize.PopulateObject(json, token);

            if (string.IsNullOrEmpty(token.OwnerLogin))
            {
                token.OwnerLogin = JToken.Parse(json)
                                   .SelectToken("owner.login")
                                   .ToString();
            }

            token = await TokenClient
                    .SetAsync(token, true)
                    .ConfigureAwait(false);

            return(new ContentResult
            {
                StatusCode = (int)HttpStatusCode.OK,
                ContentType = "text/html",
                Content = Assembly.GetExecutingAssembly().GetManifestResourceTemplate($"{GetType().FullName}_Install.html", GetFormContext())
            });
        }
        else
        {
            return(new ContentResult
            {
                StatusCode = (int)HttpStatusCode.OK,
                ContentType = "text/html",
                Content = Assembly.GetExecutingAssembly().GetManifestResourceTemplate($"{GetType().FullName}_Register.html", GetFormContext())
            });
        }

        object GetFormContext() => new
        {
            deploymentScope        = deploymentScope,
            authorizationEndpoints = authorizationEndpoints,
            token     = token,
            data      = data,
            session   = session,
            error     = queryError ?? string.Empty,
            succeeded = queryParams.Contains("succeeded")
        };
    }
    protected override async Task <Component> CreateComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue)
    {
        if (component is null)
        {
            throw new ArgumentNullException(nameof(component));
        }

        if (!AzureResourceIdentifier.TryParse(component.ResourceId, out var resourceId))
        {
            component.ResourceId = await CreateResourceIdAsync(component)
                                   .ConfigureAwait(false);

            resourceId = AzureResourceIdentifier.Parse(component.ResourceId);

            var sessionIdenity = await azureResourceService.AzureSessionService
                                 .GetIdentityAsync()
                                 .ConfigureAwait(false);

            component.ResourceUrl = resourceId.GetPortalUrl(sessionIdenity.TenantId);

            component = await componentRepository
                        .SetAsync(component)
                        .ConfigureAwait(false);
        }

        return(await UpdateComponentAsync(component, contextUser, commandQueue).ConfigureAwait(false));
    }
    private async Task <T> WithKubernetesContext <T>(Component component, DeploymentScope deploymentScope, Func <IKubernetes, KubernetesData, V1ClusterRole, V1ServiceAccount, Task <T> > callback)
    {
        if (component is null)
        {
            throw new ArgumentNullException(nameof(component));
        }

        if (deploymentScope is null)
        {
            throw new ArgumentNullException(nameof(deploymentScope));
        }

        if (callback is null)
        {
            throw new ArgumentNullException(nameof(callback));
        }

        var identity = await azureResourceService
                       .GetResourceAsync <AzureIdentityResource>(component.IdentityId, throwIfNotExists : true)
                       .ConfigureAwait(false);

        var data   = GetKubernetesData(deploymentScope);
        var client = GetKubernetesClient(data);

        var roleDefinition = new V1ClusterRole()
        {
            Metadata = new V1ObjectMeta()
            {
                Name = "teamcloud-runner"
            },
            Rules = new List <V1PolicyRule>()
            {
                new V1PolicyRule()
                {
                    ApiGroups = new List <string>()
                    {
                        "", "extensions", "apps"
                    },
                    Resources = new List <string>()
                    {
                        "*"
                    },
                    Verbs = new List <string>()
                    {
                        "*"
                    }
                },
                new V1PolicyRule()
                {
                    ApiGroups = new List <string>()
                    {
                        "batch"
                    },
                    Resources = new List <string>()
                    {
                        "jobs", "cronjobs"
                    },
                    Verbs = new List <string>()
                    {
                        "*"
                    }
                }
            }
        };

        try
        {
            roleDefinition = await client
                             .CreateClusterRoleAsync(roleDefinition)
                             .ConfigureAwait(false);
        }
        catch (HttpOperationException exc) when(exc.Response.StatusCode == System.Net.HttpStatusCode.Conflict)
        {
            roleDefinition = await client
                             .ReadClusterRoleAsync(roleDefinition.Metadata.Name)
                             .ConfigureAwait(false);
        }

        var serviceAccount = new V1ServiceAccount()
        {
            Metadata = new V1ObjectMeta()
            {
                Name = $"{data.Namespace}-{identity.PrincipalId}"
            }
        };

        try
        {
            serviceAccount = await client
                             .CreateNamespacedServiceAccountAsync(serviceAccount, data.Namespace)
                             .ConfigureAwait(false);
        }
        catch (HttpOperationException exc) when(exc.Response.StatusCode == System.Net.HttpStatusCode.Conflict)
        {
            serviceAccount = await client
                             .ReadNamespacedServiceAccountAsync(serviceAccount.Metadata.Name, data.Namespace)
                             .ConfigureAwait(false);
        }

        return(await callback(client, data, roleDefinition, serviceAccount).ConfigureAwait(false));
    }
    protected override async Task <Component> DeleteComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue)
    {
        if (component is null)
        {
            throw new ArgumentNullException(nameof(component));
        }

        if (AzureResourceIdentifier.TryParse(component.ResourceId, out var componentResourceId))
        {
            var resourceGroup = await azureResourceService
                                .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup)
                                .ConfigureAwait(false);

            if (resourceGroup is not null)
            {
                await resourceGroup
                .DeleteAsync(true)
                .ConfigureAwait(false);
            }

            // remove resource related informations

            component.ResourceId  = null;
            component.ResourceUrl = null;

            // ensure resource state is deleted

            component.ResourceState = Model.Common.ResourceState.Deprovisioned;

            // update entity to ensure we have it's state updated in case the delete fails

            component = await componentRepository
                        .SetAsync(component)
                        .ConfigureAwait(false);
        }

        return(component);
    }
    public virtual async Task <AzureServicePrincipal> GetServiceIdentityAsync(DeploymentScope deploymentScope, bool withPassword = false)
    {
        if (this is IAdapterIdentity)
        {
            var servicePrincipalKey = Guid.Parse(deploymentScope.Organization)
                                      .Combine(Guid.Parse(deploymentScope.Id));

            var servicePrincipalName = $"{this.GetType().Name}/{servicePrincipalKey}";

            var servicePrincipal = await graphService
                                   .GetServicePrincipalAsync(servicePrincipalName)
                                   .ConfigureAwait(false);

            if (servicePrincipal is null)
            {
                // there is no service principal for the current deployment scope
                // create a new one that we can use to create/update the corresponding
                // service endpoint in the current team project

                servicePrincipal = await graphService
                                   .CreateServicePrincipalAsync(servicePrincipalName)
                                   .ConfigureAwait(false);
            }
            else if (servicePrincipal.ExpiresOn.GetValueOrDefault(DateTime.MinValue) < DateTime.UtcNow)
            {
                // a service principal exists, but its secret is expired. lets refresh
                // the service principal (create a new secret) so we can move on
                // creating/updating the corresponding service endpoint.

                servicePrincipal = await graphService
                                   .RefreshServicePrincipalAsync(servicePrincipalName)
                                   .ConfigureAwait(false);
            }

            if (!string.IsNullOrEmpty(servicePrincipal.Password))
            {
                var tenantId = await azureSessionService
                               .GetTenantIdAsync()
                               .ConfigureAwait(false);

                var organization = await organizationRepository
                                   .GetAsync(tenantId.ToString(), deploymentScope.Organization)
                                   .ConfigureAwait(false);

                var secretsStore = await secretsStoreProvider
                                   .GetSecretsStoreAsync(organization)
                                   .ConfigureAwait(false);

                servicePrincipal = await secretsStore
                                   .SetSecretAsync(servicePrincipal.Id.ToString(), servicePrincipal)
                                   .ConfigureAwait(false);
            }
            else if (withPassword)
            {
                var tenantId = await azureSessionService
                               .GetTenantIdAsync()
                               .ConfigureAwait(false);

                var organization = await organizationRepository
                                   .GetAsync(tenantId.ToString(), deploymentScope.Organization)
                                   .ConfigureAwait(false);

                var secretsStore = await secretsStoreProvider
                                   .GetSecretsStoreAsync(organization)
                                   .ConfigureAwait(false);

                servicePrincipal = (await secretsStore
                                    .GetSecretAsync <AzureServicePrincipal>(servicePrincipal.Id.ToString())
                                    .ConfigureAwait(false)) ?? servicePrincipal;
            }

            return(servicePrincipal);
        }

        return(null);
    }