Blocking any customer that wants to link an AD account to an existing user account.
Version
2024.2.9206, 2024.2.9220
Latest Version
I could reproduce the problem in the latest build
What happened?
When trying to manually add a user AD account to an existing Octopus user account in the UI you get an error:
Error - A user with a matching external login already exists
I tested this with an existing Octopus account which had never had an AD account assigned to it and a brand new AD user:
New AD user:
Adding it to an existing account creates the error when trying to save the add:
If you create a brand new user and add the ad account in there during creation it will let you add the account so this bug is only applicable when trying to add an AD account to an existing Octopus user.
I also tried to create a new Octopus account in the UI with no AD account linked, then once created tried to add the AD account and you do get the same error.
Reproduction
Create a new account in Active Directory for testing use.
Go into Configuration > Users and pick an existing user who does not have an AD account associated with it (if you do not have one create one first).
Try adding that newly created AD account to the existing Octopus Account.
Save that change and see the error.
Error and Stacktrace
2024-06-13 13:22:04.4779 7692 22 INFO "HTTP" "PUT" to "192.168.0.201""/api/users/Users-284" "completed" with 400 in 00:00:00.0185048 (18ms) by "octoadmin"
Octopus.Core.Model.Exceptions.DomainException: A user with a matching external login already exists.
at Octopus.Core.Features.Users.ModifyUserCommandHandler.ModifyUser(ModifyUserCommand command, CancellationToken cancellationToken) in ./source/Octopus.Core/Features/Users/ModifyUserCommandHandler.cs:line 75
at Octopus.Core.Features.Users.ModifyUserCommandHandler.Handle(ModifyUserCommand command, CancellationToken cancellationToken) in ./source/Octopus.Core/Features/Users/ModifyUserCommandHandler.cs:line 49
at Octopus.Core.Infrastructure.Mediator.AutofacMediator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/AutofacMediator.cs:line 33
at Octopus.Core.Infrastructure.Mediator.Decorators.SystemComponentModelValidationDecorator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/Decorators/SystemComponentModelValidationDecorator.cs:line 28
at Octopus.Core.Infrastructure.Mediator.Decorators.FluentValidationsDecorator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/Decorators/FluentValidationsDecorator.cs:line 35
at Octopus.Core.Infrastructure.Mediator.Decorators.MessageBusSiphoningDecorator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/Decorators/MessageBusSiphoningDecorator.cs:line 32
at Octopus.Server.Web.Controllers.Users.ModifyUserController.ModifyUser(ModifyUserCommand command, CancellationToken cancellationToken) in ./source/Octopus.Server/Web/Controllers/Users/ModifyUserController.cs:line 23
at lambda_method10855(Closure, Object)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Octopus.Server.Web.Middleware.BoundaryTrailerRewriteMiddleware.Invoke(HttpContext context, IAutomationContext automationContext) in ./source/Octopus.Server/Web/Middleware/BoundaryTrailerRewriteMiddleware.cs:line 45
at Octopus.Server.Web.Infrastructure.Authentication.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) in ./source/Octopus.Server/Web/Infrastructure/Authentication/AuthorizationMiddlewareResultHandler.cs:line 50
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Octopus.Server.Web.UnitOfWorkMiddleware.InvokeAsync(HttpContext httpContext, IUnitOfWork unitOfWork) in ./source/Octopus.Server/Web/UnitOfWorkMiddleware.cs:line 31
at Octopus.Server.Web.UnitOfWorkMiddleware.InvokeAsync(HttpContext httpContext, IUnitOfWork unitOfWork) in ./source/Octopus.Server/Web/UnitOfWorkMiddleware.cs:line 42
at Octopus.Server.Web.Middleware.OctopusClientOldVersionWarningMiddleware.InvokeAsync(HttpContext context, IAutomationContext automationContext) in ./source/Octopus.Server/Web/Middleware/OctopusClientOldVersionWarningMiddleware.cs:line 24
at Octopus.Server.Web.Middleware.DynamicContentHeadersMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/DynamicContentHeadersMiddleware.cs:line 50
at Octopus.Server.Web.Middleware.MaintenanceModeMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/MaintenanceModeMiddleware.cs:line 58
at Octopus.Server.Web.Middleware.OctopusAuthenticationMiddleware.InvokeAsync(HttpContext context, IUserAuthenticator userAuthenticator, IUserSessionService userSessionService, IWebAuthCache authCache, ILogger logger) in ./source/Octopus.Server/Web/Middleware/OctopusAuthenticationMiddleware.cs:line 61
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Octopus.Server.Web.Middleware.LegacyRequestLoggerMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/LegacyRequestLoggerMiddleware.cs:line 35
at Octopus.Server.Web.Middleware.TelemetryMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/TelemetryMiddleware.cs:line 64
at Octopus.Server.Web.Middleware.ErrorHandlingMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/ErrorHandlingMiddleware.cs:line 51
2024-06-13 13:22:04.4779 7692 22 INFO "HTTP" "POST" to "192.168.0.201""/bff/telemetry/recordPortalOperation" "completed" with 200 in 00:00:00.0080430 (8ms) by "octoadmin"
2024-06-13 13:22:04.4779 7692 22 INFO "A user with a matching external login already exists."
Octopus.Core.Model.Exceptions.DomainException: A user with a matching external login already exists.
at Octopus.Core.Features.Users.ModifyUserCommandHandler.ModifyUser(ModifyUserCommand command, CancellationToken cancellationToken) in ./source/Octopus.Core/Features/Users/ModifyUserCommandHandler.cs:line 75
at Octopus.Core.Features.Users.ModifyUserCommandHandler.Handle(ModifyUserCommand command, CancellationToken cancellationToken) in ./source/Octopus.Core/Features/Users/ModifyUserCommandHandler.cs:line 49
at Octopus.Core.Infrastructure.Mediator.AutofacMediator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/AutofacMediator.cs:line 33
at Octopus.Core.Infrastructure.Mediator.Decorators.SystemComponentModelValidationDecorator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/Decorators/SystemComponentModelValidationDecorator.cs:line 28
at Octopus.Core.Infrastructure.Mediator.Decorators.FluentValidationsDecorator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/Decorators/FluentValidationsDecorator.cs:line 35
at Octopus.Core.Infrastructure.Mediator.Decorators.MessageBusSiphoningDecorator.Do[TCommand,TResponse](ICommand`2 command, CancellationToken cancellationToken) in ./source/Octopus.Core/Infrastructure/Mediator/Decorators/MessageBusSiphoningDecorator.cs:line 32
at Octopus.Server.Web.Controllers.Users.ModifyUserController.ModifyUser(ModifyUserCommand command, CancellationToken cancellationToken) in ./source/Octopus.Server/Web/Controllers/Users/ModifyUserController.cs:line 23
at lambda_method10855(Closure, Object)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Octopus.Server.Web.Middleware.BoundaryTrailerRewriteMiddleware.Invoke(HttpContext context, IAutomationContext automationContext) in ./source/Octopus.Server/Web/Middleware/BoundaryTrailerRewriteMiddleware.cs:line 45
at Octopus.Server.Web.Infrastructure.Authentication.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) in ./source/Octopus.Server/Web/Infrastructure/Authentication/AuthorizationMiddlewareResultHandler.cs:line 50
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Octopus.Server.Web.UnitOfWorkMiddleware.InvokeAsync(HttpContext httpContext, IUnitOfWork unitOfWork) in ./source/Octopus.Server/Web/UnitOfWorkMiddleware.cs:line 31
at Octopus.Server.Web.UnitOfWorkMiddleware.InvokeAsync(HttpContext httpContext, IUnitOfWork unitOfWork) in ./source/Octopus.Server/Web/UnitOfWorkMiddleware.cs:line 42
at Octopus.Server.Web.Middleware.OctopusClientOldVersionWarningMiddleware.InvokeAsync(HttpContext context, IAutomationContext automationContext) in ./source/Octopus.Server/Web/Middleware/OctopusClientOldVersionWarningMiddleware.cs:line 24
at Octopus.Server.Web.Middleware.DynamicContentHeadersMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/DynamicContentHeadersMiddleware.cs:line 50
at Octopus.Server.Web.Middleware.MaintenanceModeMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/MaintenanceModeMiddleware.cs:line 58
at Octopus.Server.Web.Middleware.OctopusAuthenticationMiddleware.InvokeAsync(HttpContext context, IUserAuthenticator userAuthenticator, IUserSessionService userSessionService, IWebAuthCache authCache, ILogger logger) in ./source/Octopus.Server/Web/Middleware/OctopusAuthenticationMiddleware.cs:line 61
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Octopus.Server.Web.Middleware.LegacyRequestLoggerMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/LegacyRequestLoggerMiddleware.cs:line 35
at Octopus.Server.Web.Middleware.TelemetryMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/TelemetryMiddleware.cs:line 64
at Octopus.Server.Web.Middleware.ErrorHandlingMiddleware.InvokeAsync(HttpContext context) in ./source/Octopus.Server/Web/Middleware/ErrorHandlingMiddleware.cs:line 51
Trying to add the AD account via the API will probably fail with the same error message since adding via the API would perform the same actions as adding it through the UI.
Unfortunately due to the nature of the user data for AD being in three separate Octopus DB tables (dbo.User, dbo.ExternalProvider, dbo.ExternalProviderSecurityGroup) and that user being apart of the audit log we want to consult our engineers for the best course of action for a workaround which allows a user to keep their original accounts (this issue will be updated once this has been discussed).
At the moment the safest way to resolve this would be to delete that user and re-create them, ensuring you add the AD account during the creation of that user.
Things to note on this workaround -
When you delete that user and create a new one the User-ID for that user will be changed and so that will need to be accounted for if you use the audit log extensively.
That users API keys will be deleted so they will have to be re-created and then changed in whatever application you had them in.
Externally mapped permissions will be re-created on logon but teams assigned to the account directly will be removed and will need to be re-added so you will need to list what teams that user is in before you delete them (this will be in the audit log if you forget).
Please be aware of those considerations when deleting user accounts. Hopefully we can get a better workaround very soon.
Severity
Blocking any customer that wants to link an AD account to an existing user account.
Version
2024.2.9206, 2024.2.9220
Latest Version
I could reproduce the problem in the latest build
What happened?
When trying to manually add a user AD account to an existing Octopus user account in the UI you get an error:
Error - A user with a matching external login already exists
I tested this with an existing Octopus account which had never had an AD account assigned to it and a brand new AD user:
New AD user:
Adding it to an existing account creates the error when trying to save the add:
If you create a brand new user and add the ad account in there during creation it will let you add the account so this bug is only applicable when trying to add an AD account to an existing Octopus user.
I also tried to create a new Octopus account in the UI with no AD account linked, then once created tried to add the AD account and you do get the same error.
Reproduction
Configuration > Users
and pick an existing user who does not have an AD account associated with it (if you do not have one create one first).Error and Stacktrace
More Information
Initial user ticket (internal) - https://octopus.zendesk.com/agent/tickets/190752 R and D request (Internal) - https://octopusdeploy.slack.com/archives/CNHBHV2BX/p1718291062898179
Workaround
Trying to add the AD account via the API will probably fail with the same error message since adding via the API would perform the same actions as adding it through the UI.
Unfortunately due to the nature of the user data for AD being in three separate Octopus DB tables (dbo.User, dbo.ExternalProvider, dbo.ExternalProviderSecurityGroup) and that user being apart of the audit log we want to consult our engineers for the best course of action for a workaround which allows a user to keep their original accounts (this issue will be updated once this has been discussed).
At the moment the safest way to resolve this would be to delete that user and re-create them, ensuring you add the AD account during the creation of that user.
Things to note on this workaround -
Please be aware of those considerations when deleting user accounts. Hopefully we can get a better workaround very soon.