public async Task <bool> AuthenticateAsync(string id, Option <string> modelId, Option <string> authChain) { try { // See if we received direct credentials for the target identity bool hasToken = this.clientTokens.TryGetValue(id, out ClientToken clientToken); // Otherwise, this could be a child Edge acting OnBehalfOf of the target, // in which case we would have the actor's credentials instead if (!hasToken) { Option <string> actorDeviceId = AuthChainHelpers.GetActorDeviceId(authChain); hasToken = actorDeviceId.Map(actor => this.clientTokens.TryGetValue($"{actor}/{CoreConstants.EdgeHubModuleId}", out clientToken)).GetOrElse(false); } if (hasToken) { // Lock the auth-state check and the auth call to avoid // redundant calls into the authenticator using (await this.identitySyncLock.LockAsync()) { if (this.authenticatedClients.ContainsKey(id)) { // We've previously authenticated this client already return(true); } else { IClientCredentials clientCredentials = this.clientCredentialsFactory.GetWithSasToken( clientToken.DeviceId, clientToken.ModuleId.OrDefault(), string.Empty, clientToken.Token, true, modelId, authChain); if (await this.authenticator.AuthenticateAsync(clientCredentials)) { // Authentication success, add an entry for the authenticated // client identity, the value in the dictionary doesn't matter // as we're effectively using it as a thread-safe HashSet this.authenticatedClients[id] = default(byte); // No need to add the new credentials to the cache, as the // authenticator already implicitly handles it return(true); } } } } return(false); } catch (Exception e) { Events.ErrorAuthenticatingIdentity(id, e); return(false); } }
async Task <bool> AuthorizeActorAsync(IDeviceScopeIdentitiesCache identitiesCache, string actorDeviceId, string actorModuleId, string targetId) { if (actorModuleId != Constants.EdgeHubModuleId) { // Only child EdgeHubs are allowed to act OnBehalfOf of devices/modules. Events.AuthFail_BadActor(actorDeviceId, actorModuleId, targetId); return(false); } // Actor device is claiming to be our child, and that the target device is its child. // So we should have an authchain already cached for the target device. Option <string> targetAuthChainOption = await identitiesCache.GetAuthChain(targetId); if (!targetAuthChainOption.HasValue) { Events.AuthFail_NoAuthChain(targetId); return(false); } // Validate the target auth-chain string targetAuthChain = targetAuthChainOption.Expect(() => new InvalidOperationException()); if (!AuthChainHelpers.ValidateAuthChain(actorDeviceId, targetId, targetAuthChain)) { Events.AuthFail_InvalidAuthChain(actorDeviceId, targetId, targetAuthChain); return(false); } return(true); }
internal static async Task <EdgeHubScopeResult> HandleDevicesAndModulesInTargetDeviceScopeAsync(string actorDeviceId, string actorModuleId, NestedScopeRequest request, IDeviceScopeIdentitiesCache identitiesCache) { Events.ReceivedScopeRequest(actorDeviceId, actorModuleId, request); if (!AuthChainHelpers.TryGetTargetDeviceId(request.AuthChain, out string targetDeviceId)) { return(new EdgeHubScopeResultError(HttpStatusCode.BadRequest, Events.InvalidRequestAuthchain(request.AuthChain))); } // Get the children of the target device and the target device itself; Option <string> authChainToTarget = await identitiesCache.GetAuthChain(targetDeviceId); (bool validationResult, string errorMsg) = ValidateAuthChainForRequestor(actorDeviceId, targetDeviceId, authChainToTarget); if (!validationResult) { return(new EdgeHubScopeResultError(HttpStatusCode.Unauthorized, errorMsg)); } IList <ServiceIdentity> identities = await identitiesCache.GetDevicesAndModulesInTargetScopeAsync(targetDeviceId); Option <ServiceIdentity> targetDevice = await identitiesCache.GetServiceIdentity(targetDeviceId); targetDevice.ForEach(d => identities.Add(d)); // Construct the result from the identities Events.SendingScopeResult(targetDeviceId, identities); return(MakeResultFromIdentities(identities)); }
public void TryGetTargetDeviceId_Success(string authChain, bool expected, string expectedTargetDeviceId) { bool actual = AuthChainHelpers.TryGetTargetDeviceId(authChain, out string actualTargetDeviceId); Assert.Equal(expected, actual); if (actual) { Assert.Equal(expectedTargetDeviceId, actualTargetDeviceId); } }
internal bool ValidateAuthChain(string actorDeviceId, string targetId, string authChain) { if (AuthChainHelpers.ValidateAuthChain(actorDeviceId, targetId, authChain)) { return(true); } else { Events.InvalidAuthChain(targetId, authChain); return(false); } }
public void ValidateChainTest() { // Correct case Assert.True(AuthChainHelpers.ValidateAuthChain("edge1", "leaf1", "leaf1;edge1;edgeRoot")); // Unauthorized actor Assert.False(AuthChainHelpers.ValidateAuthChain("edge1", "leaf1", "leaf1;edge2;edgeRoot")); // Bad target Assert.False(AuthChainHelpers.ValidateAuthChain("edge1", "leaf1", "leaf2;edge1;edgeRoot")); // Invalid format Assert.False(AuthChainHelpers.ValidateAuthChain("edge1", "leaf1", ";")); }
public async Task GetDevicesAndModulesInTargetDeviceScopeAsync([FromRoute] string actorDeviceId, [FromRoute] string actorModuleId, [FromBody] NestedScopeRequest request) { actorDeviceId = WebUtility.UrlDecode(Preconditions.CheckNonWhiteSpace(actorDeviceId, nameof(actorDeviceId))); actorModuleId = WebUtility.UrlDecode(Preconditions.CheckNonWhiteSpace(actorModuleId, nameof(actorModuleId))); Preconditions.CheckNonWhiteSpace(request.AuthChain, nameof(request.AuthChain)); if (actorModuleId != Constants.EdgeHubModuleId) { // Only child EdgeHubs are allowed to act OnBehalfOf of devices/modules. var result = new EdgeHubScopeResultError(HttpStatusCode.Unauthorized, Events.UnauthorizedActor(actorDeviceId, actorModuleId)); await this.SendResponse(result.Status, JsonConvert.SerializeObject(result)); } string authChain = request.AuthChain; string[] ids = AuthChainHelpers.GetAuthChainIds(authChain); if (ids.Length == 1) { // A child EdgeHub can use its module credentials to calls upstream // OnBehalfOf its device identity, so the auth-chain would just have // one element denoting the target device scope but no actor. // However, the auth stack requires an actor to be specified for OnBehalfOf // connections, so we manually add the actor to the auth-chain for this // special case. authChain = $"{ids[0]}/{Constants.EdgeHubModuleId};{ids[0]}"; } IHttpRequestAuthenticator authenticator = await this.authenticatorGetter; HttpAuthResult authResult = await authenticator.AuthenticateAsync(actorDeviceId, Option.Some(actorModuleId), Option.Some(authChain), this.HttpContext); if (authResult.Authenticated) { EdgeHubScopeResult reqResult = await this.HandleDevicesAndModulesInTargetDeviceScopeAsync(actorDeviceId, actorModuleId, request); await this.SendResponse(reqResult.Status, JsonConvert.SerializeObject(reqResult)); } else { var result = new EdgeHubScopeResultError(HttpStatusCode.Unauthorized, authResult.ErrorMessage); await this.SendResponse(result.Status, JsonConvert.SerializeObject(result)); } }
async Task <EdgeHubScopeResult> HandleDevicesAndModulesInTargetDeviceScopeAsync(string actorDeviceId, string actorModuleId, NestedScopeRequest request) { Events.ReceivedScopeRequest(actorDeviceId, actorModuleId, request); if (!AuthChainHelpers.TryGetTargetDeviceId(request.AuthChain, out string targetDeviceId)) { return(new EdgeHubScopeResultError(HttpStatusCode.BadRequest, Events.InvalidRequestAuthchain(request.AuthChain))); } // Get the children of the target device and the target device itself; IEdgeHub edgeHub = await this.edgeHubGetter; IDeviceScopeIdentitiesCache identitiesCache = edgeHub.GetDeviceScopeIdentitiesCache(); IList <ServiceIdentity> identities = await identitiesCache.GetDevicesAndModulesInTargetScopeAsync(targetDeviceId); Option <ServiceIdentity> targetDevice = await identitiesCache.GetServiceIdentity(targetDeviceId); targetDevice.ForEach(d => identities.Add(d)); // Construct the result from the identities Events.SendingScopeResult(targetDeviceId, identities); return(MakeResultFromIdentities(identities)); }
internal static async Task <Try <string> > AuthorizeOnBehalfOf( string actorDeviceId, string authChain, string source, HttpContext httpContext, IEdgeHub edgeHub, IHttpRequestAuthenticator authenticator) { if (!AuthChainHelpers.TryGetTargetDeviceId(authChain, out string targetDeviceId)) { Events.InvalidRequestAuthChain(source, authChain); return(Try <string> .Failure(new ValidationException(HttpStatusCode.BadRequest, FormatErrorResponseMessage($"Invalid request auth chain {authChain}.")))); } if (!await AuthenticateAsync(actorDeviceId, Option.Some(Constants.EdgeHubModuleId), Option.Some(authChain), httpContext, authenticator)) { return(Try <string> .Failure(new ValidationException(HttpStatusCode.Unauthorized))); } IDeviceScopeIdentitiesCache identitiesCache = edgeHub.GetDeviceScopeIdentitiesCache(); Option <string> targetAuthChain = await identitiesCache.GetAuthChain(targetDeviceId); return(targetAuthChain.Match( ac => { if (!AuthChainHelpers.ValidateAuthChain(actorDeviceId, targetDeviceId, ac)) { Events.AuthorizationFail_InvalidAuthChain(actorDeviceId, targetDeviceId, ac); return Try <string> .Failure(new ValidationException(HttpStatusCode.Unauthorized)); } return ac; }, () => { Events.AuthorizationFail_NoAuthChain(targetDeviceId); return Try <string> .Failure(new ValidationException(HttpStatusCode.Unauthorized)); })); }
public async Task <IDeviceIdentity> GetAsync(string clientId, string username, string password, EndPoint clientAddress) { try { Preconditions.CheckNonWhiteSpace(username, nameof(username)); Preconditions.CheckNonWhiteSpace(clientId, nameof(clientId)); ClientInfo clientInfo = this.usernameParser.Parse(username); clientInfo.ModelId.ForEach(async m => await this.metadataStore.SetModelId(clientInfo.DeviceId, m)); IClientCredentials deviceCredentials = null; Option <IClientCredentials> actorCredentials = Option.None <IClientCredentials>(); if (!string.IsNullOrEmpty(password)) { deviceCredentials = this.clientCredentialsFactory.GetWithSasToken(clientInfo.DeviceId, clientInfo.ModuleId, clientInfo.DeviceClientType, password, false, clientInfo.ModelId, clientInfo.AuthChain); // For OnBehalfOf connections, we'll get the token for the actor EdgeHub instead // of the actual leaf/module, so we need to construct the credentials accordingly Option <string> actorDeviceIdOption = AuthChainHelpers.GetActorDeviceId(clientInfo.AuthChain); actorCredentials = actorDeviceIdOption.Map(actorDeviceId => this.clientCredentialsFactory.GetWithSasToken( actorDeviceId, Microsoft.Azure.Devices.Edge.Hub.Core.Constants.EdgeHubModuleId, clientInfo.DeviceClientType, password, false, clientInfo.ModelId, clientInfo.AuthChain)); } else if (this.remoteCertificate.HasValue) { if (!this.clientCertAuthAllowed) { Events.CertAuthNotEnabled(clientInfo.DeviceId, clientInfo.ModuleId); return(UnauthenticatedDeviceIdentity.Instance); } this.remoteCertificate.ForEach( cert => { deviceCredentials = this.clientCredentialsFactory.GetWithX509Cert( clientInfo.DeviceId, clientInfo.ModuleId, clientInfo.DeviceClientType, cert, this.remoteCertificateChain, clientInfo.ModelId, Option.None <string>()); }); } else { Events.AuthNotFound(clientInfo.DeviceId, clientInfo.ModuleId); return(UnauthenticatedDeviceIdentity.Instance); } if (deviceCredentials == null || !clientId.Equals(deviceCredentials.Identity.Id, StringComparison.Ordinal) || !await this.authenticator.AuthenticateAsync(actorCredentials.GetOrElse(deviceCredentials))) { Events.Error(clientId, username); return(UnauthenticatedDeviceIdentity.Instance); } await this.metadataStore.SetMetadata(deviceCredentials.Identity.Id, clientInfo.DeviceClientType, clientInfo.ModelId); Events.Success(clientId, username); return(new ProtocolGatewayIdentity(deviceCredentials, clientInfo.ModelId)); } catch (Exception ex) { Events.ErrorCreatingIdentity(ex); throw; } }
public void GetAuthChainIds_Fail(string authChain) { Assert.Throws <ArgumentException>(() => AuthChainHelpers.GetAuthChainIds(authChain)); }
public void GetAuthChainIds_Success() { Assert.Equal(new[] { "device1/$edgeHub", "device1", "device2" }, AuthChainHelpers.GetAuthChainIds("device1/$edgeHub;device1;device2")); Assert.Equal(new[] { "longdevicename" }, AuthChainHelpers.GetAuthChainIds("longdevicename")); }
public async Task DeleteModuleOnBehalfOfAsync( [FromRoute] string actorDeviceId, [FromBody] DeleteModuleOnBehalfOfData requestData) { try { Events.ReceivedOnBehalfOfRequest(nameof(this.DeleteModuleOnBehalfOfAsync), actorDeviceId, Events.GetAdditionalInfo(requestData)); try { Preconditions.CheckNonWhiteSpace(actorDeviceId, nameof(actorDeviceId)); Preconditions.CheckNotNull(requestData, nameof(requestData)); Preconditions.CheckNonWhiteSpace(requestData.AuthChain, nameof(requestData.AuthChain)); Preconditions.CheckNonWhiteSpace(requestData.ModuleId, nameof(requestData.ModuleId)); } catch (Exception ex) { Events.BadRequest(nameof(this.DeleteModuleOnBehalfOfAsync), ex.Message); await this.SendResponseAsync(HttpStatusCode.BadRequest, FormatErrorResponseMessage(ex.ToString())); return; } actorDeviceId = WebUtility.UrlDecode(actorDeviceId); if (!AuthChainHelpers.TryGetTargetDeviceId(requestData.AuthChain, out string targetDeviceId)) { Events.InvalidRequestAuthChain(nameof(this.DeleteModuleOnBehalfOfAsync), requestData.AuthChain); await this.SendResponseAsync(HttpStatusCode.BadRequest, FormatErrorResponseMessage($"Invalid request auth chain {requestData.AuthChain}.")); return; } if (!await this.AuthenticateAsync(actorDeviceId, Option.Some(Constants.EdgeHubModuleId), Option.Some(requestData.AuthChain))) { await this.SendResponseAsync(HttpStatusCode.Unauthorized); return; } IEdgeHub edgeHub = await this.edgeHubGetter; IDeviceScopeIdentitiesCache identitiesCache = edgeHub.GetDeviceScopeIdentitiesCache(); Option <string> targetAuthChain = await identitiesCache.GetAuthChain(targetDeviceId); if (!targetAuthChain.HasValue) { Events.AuthorizationFail_NoAuthChain(targetDeviceId); await this.SendResponseAsync(HttpStatusCode.Unauthorized); return; } string edgeDeviceId = edgeHub.GetEdgeDeviceId(); RegistryApiHttpResult result = await this.apiClient.DeleteModuleAsync( edgeDeviceId, new DeleteModuleOnBehalfOfData($"{targetAuthChain.OrDefault()}", requestData.ModuleId)); await this.SendResponseAsync(result.StatusCode, result.JsonContent); Events.CompleteRequest(nameof(this.DeleteModuleOnBehalfOfAsync), edgeDeviceId, targetAuthChain.OrDefault(), result); } catch (Exception ex) { Events.InternalServerError(nameof(this.DeleteModuleOnBehalfOfAsync), ex); await this.SendResponseAsync(HttpStatusCode.InternalServerError, FormatErrorResponseMessage(ex.ToString())); } }