1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-28 13:15:12 +01:00

Create sso user api (#886)

* facilitate linking/unlinking existing users from an sso enabled org

* added user_identifier to identity methods for sso

* moved sso user delete method to account controller

* fixed a broken test

* Update AccountsController.cs

* facilitate linking/unlinking existing users from an sso enabled org

* added user_identifier to identity methods for sso

* moved sso user delete method to account controller

* fixed a broken test

* added a token to the existing user sso link flow

* added a token to the existing user sso link flow

* fixed a typo

* added an event log for unlink ssoUser records

* fixed a merge issue

* fixed a busted test

* fixed a busted test

* ran a formatter over everything & changed .vscode settings in .gitignore

* chagned a variable to use string interpolation

* removed a blank line

* Changed TokenPurpose enum to a static class of strings

* code review cleanups

* formatting fix

* Changed parameters & logging for delete sso user

* changed th method used to get organization user for deleting sso user records

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
Addison Beck 2020-08-26 14:12:04 -04:00 committed by GitHub
parent 7cc9ce7bd5
commit 59f8467f7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 214 additions and 64 deletions

2
.gitignore vendored
View File

@ -207,3 +207,5 @@ src/Core/Properties/launchSettings.json
**/*.DS_Store **/*.DS_Store
src/Admin/wwwroot/lib src/Admin/wwwroot/lib
src/Admin/wwwroot/css src/Admin/wwwroot/css
.vscode/*
**/.vscode/*

View File

@ -1,21 +1,22 @@
using System; using Bit.Api.Utilities;
using System.Threading.Tasks; using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api;
using Bit.Core.Models.Api.Request.Accounts;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Bit.Core.Models.Api; using System;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core.Enums;
using System.Linq;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Bit.Core;
using Bit.Core.Models.Business;
using Bit.Api.Utilities;
using Bit.Core.Models.Table;
using System.Collections.Generic; using System.Collections.Generic;
using Bit.Core.Models.Api.Request.Accounts; using System.Linq;
using Bit.Core.Models.Data; using System.Text;
using System.Threading.Tasks;
namespace Bit.Api.Controllers namespace Bit.Api.Controllers
{ {
@ -23,30 +24,34 @@ namespace Bit.Api.Controllers
[Authorize("Application")] [Authorize("Application")]
public class AccountsController : Controller public class AccountsController : Controller
{ {
private readonly IUserService _userService; private readonly GlobalSettings _globalSettings;
private readonly IUserRepository _userRepository;
private readonly ICipherRepository _cipherRepository; private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository; private readonly IFolderRepository _folderRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService; private readonly IPaymentService _paymentService;
private readonly GlobalSettings _globalSettings; private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
public AccountsController( public AccountsController(
IUserService userService, GlobalSettings globalSettings,
IUserRepository userRepository,
ICipherRepository cipherRepository, ICipherRepository cipherRepository,
IFolderRepository folderRepository, IFolderRepository folderRepository,
IOrganizationService organizationService,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IPaymentService paymentService, IPaymentService paymentService,
GlobalSettings globalSettings) ISsoUserRepository ssoUserRepository,
IUserRepository userRepository,
IUserService userService)
{ {
_userService = userService;
_userRepository = userRepository;
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
_folderRepository = folderRepository; _folderRepository = folderRepository;
_globalSettings = globalSettings;
_organizationService = organizationService;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_paymentService = paymentService; _paymentService = paymentService;
_globalSettings = globalSettings; _userRepository = userRepository;
_userService = userService;
} }
[HttpPost("prelogin")] [HttpPost("prelogin")]
@ -708,5 +713,27 @@ namespace Bit.Api.Controllers
}; };
await _paymentService.SaveTaxInfoAsync(user, taxInfo); await _paymentService.SaveTaxInfoAsync(user, taxInfo);
} }
[HttpDelete("sso/{organizationId}")]
public async Task DeleteSsoUser(string organizationId)
{
var userId = _userService.GetProperUserId(User);
if (!userId.HasValue)
{
throw new NotFoundException();
}
await _organizationService.DeleteSsoUserAsync(userId.Value, new Guid(organizationId));
}
[HttpGet("sso/user-identifier")]
public async Task<string> GetSsoUserIdentifier()
{
var user = await _userService.GetUserByPrincipalAsync(User);
var token = await _userService.GenerateSignInTokenAsync(user, TokenPurposes.LinkSso);
var bytes = Encoding.UTF8.GetBytes($"{user.Id},{token}");
var userIdentifier = Convert.ToBase64String(bytes);
return userIdentifier;
}
} }
} }

View File

@ -4,4 +4,9 @@
{ {
public const int BypassFiltersEventId = 12482444; public const int BypassFiltersEventId = 12482444;
} }
public static class TokenPurposes
{
public const string LinkSso = "LinkSso";
}
} }

View File

@ -42,6 +42,7 @@
OrganizationUser_Updated = 1502, OrganizationUser_Updated = 1502,
OrganizationUser_Removed = 1503, OrganizationUser_Removed = 1503,
OrganizationUser_UpdatedGroups = 1504, OrganizationUser_UpdatedGroups = 1504,
OrganizationUser_UnlinkedSso = 1505,
Organization_Updated = 1600, Organization_Updated = 1600,
Organization_PurgedVault = 1601, Organization_PurgedVault = 1601,

View File

@ -28,6 +28,7 @@ namespace Bit.Core.Models.Api
Type = organization.Type; Type = organization.Type;
Enabled = organization.Enabled; Enabled = organization.Enabled;
SsoBound = !string.IsNullOrWhiteSpace(organization.SsoExternalId); SsoBound = !string.IsNullOrWhiteSpace(organization.SsoExternalId);
Identifier = organization.Identifier;
} }
public string Id { get; set; } public string Id { get; set; }
@ -51,5 +52,6 @@ namespace Bit.Core.Models.Api
public OrganizationUserType Type { get; set; } public OrganizationUserType Type { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public bool SsoBound { get; set; } public bool SsoBound { get; set; }
public string Identifier { get; set; }
} }
} }

View File

@ -25,5 +25,6 @@ namespace Bit.Core.Models.Data
public Enums.OrganizationUserType Type { get; set; } public Enums.OrganizationUserType Type { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string SsoExternalId { get; set; } public string SsoExternalId { get; set; }
public string Identifier { get; set; }
} }
} }

View File

@ -1,8 +1,11 @@
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using System;
using System.Threading.Tasks;
namespace Bit.Core.Repositories namespace Bit.Core.Repositories
{ {
public interface ISsoUserRepository : IRepository<SsoUser, long> public interface ISsoUserRepository : IRepository<SsoUser, long>
{ {
Task DeleteAsync(Guid userId, Guid? organizationId);
} }
} }

View File

@ -1,4 +1,9 @@
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Dapper;
using System;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Data;
namespace Bit.Core.Repositories.SqlServer namespace Bit.Core.Repositories.SqlServer
{ {
@ -11,5 +16,16 @@ namespace Bit.Core.Repositories.SqlServer
public SsoUserRepository(string connectionString, string readOnlyConnectionString) public SsoUserRepository(string connectionString, string readOnlyConnectionString)
: base(connectionString, readOnlyConnectionString) : base(connectionString, readOnlyConnectionString)
{ } { }
public async Task DeleteAsync(Guid userId, Guid? organizationId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[SsoUser_Delete]",
new { UserId = userId, OrganizationId = organizationId },
commandType: CommandType.StoredProcedure);
}
}
} }
} }

View File

@ -51,5 +51,6 @@ namespace Bit.Core.Services
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds, IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
bool overwriteExisting); bool overwriteExisting);
Task RotateApiKeyAsync(Organization organization); Task RotateApiKeyAsync(Organization organization);
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
} }
} }

View File

@ -68,5 +68,6 @@ namespace Bit.Core.Services
Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user); Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user);
Task<bool> TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user); Task<bool> TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user);
Task<string> GenerateEnterprisePortalSignInTokenAsync(User user); Task<string> GenerateEnterprisePortalSignInTokenAsync(User user);
Task<string> GenerateSignInTokenAsync(User user, string purpose);
} }
} }

View File

@ -35,6 +35,7 @@ namespace Bit.Core.Services
private readonly IPaymentService _paymentService; private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
@ -56,6 +57,7 @@ namespace Bit.Core.Services
IPaymentService paymentService, IPaymentService paymentService,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
ISsoUserRepository ssoUserRepository,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
GlobalSettings globalSettings) GlobalSettings globalSettings)
{ {
@ -76,6 +78,7 @@ namespace Bit.Core.Services
_paymentService = paymentService; _paymentService = paymentService;
_policyRepository = policyRepository; _policyRepository = policyRepository;
_ssoConfigRepository = ssoConfigRepository; _ssoConfigRepository = ssoConfigRepository;
_ssoUserRepository = ssoUserRepository;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }
@ -1497,6 +1500,19 @@ namespace Bit.Core.Services
await ReplaceAndUpdateCache(organization); await ReplaceAndUpdateCache(organization);
} }
public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId)
{
await _ssoUserRepository.DeleteAsync(userId, organizationId);
if (organizationId.HasValue)
{
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId);
if (organizationUser != null)
{
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UnlinkedSso);
}
}
}
private async Task UpdateUsersAsync(Group group, HashSet<string> groupUsers, private async Task UpdateUsersAsync(Group group, HashSet<string> groupUsers,
Dictionary<string, Guid> existingUsersIdDict, HashSet<Guid> existingUsers = null) Dictionary<string, Guid> existingUsersIdDict, HashSet<Guid> existingUsers = null)
{ {

View File

@ -1087,6 +1087,7 @@ namespace Bit.Core.Services
return await CanAccessPremium(user); return await CanAccessPremium(user);
} }
//TODO refactor this to use the below method and enum
public async Task<string> GenerateEnterprisePortalSignInTokenAsync(User user) public async Task<string> GenerateEnterprisePortalSignInTokenAsync(User user)
{ {
var token = await GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, var token = await GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
@ -1094,6 +1095,14 @@ namespace Bit.Core.Services
return token; return token;
} }
public async Task<string> GenerateSignInTokenAsync(User user, string purpose)
{
var token = await GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
purpose);
return token;
}
private async Task<IdentityResult> UpdatePasswordHash(User user, string newPassword, private async Task<IdentityResult> UpdatePasswordHash(User user, string newPassword,
bool validatePassword = true, bool refreshStamp = true) bool validatePassword = true, bool refreshStamp = true)
{ {

View File

@ -1,62 +1,75 @@
using System; using Bit.Core.Models.Table;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Bit.Core.Models.Table;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Identity.Models; using Bit.Identity.Models;
using IdentityModel; using IdentityModel;
using IdentityServer4; using IdentityServer4;
using IdentityServer4.Services; using IdentityServer4.Services;
using IdentityServer4.Stores; using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Bit.Identity.Controllers namespace Bit.Identity.Controllers
{ {
public class AccountController : Controller public class AccountController : Controller
{ {
private readonly IIdentityServerInteractionService _interaction;
private readonly IUserRepository _userRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IClientStore _clientStore; private readonly IClientStore _clientStore;
private readonly IIdentityServerInteractionService _interaction;
private readonly ILogger<AccountController> _logger; private readonly ILogger<AccountController> _logger;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IUserRepository _userRepository;
public AccountController( public AccountController(
IIdentityServerInteractionService interaction,
IUserRepository userRepository,
ISsoConfigRepository ssoConfigRepository,
IClientStore clientStore, IClientStore clientStore,
ILogger<AccountController> logger) IIdentityServerInteractionService interaction,
ILogger<AccountController> logger,
IOrganizationUserRepository organizationUserRepository,
ISsoConfigRepository ssoConfigRepository,
IUserRepository userRepository,
IUserService userService)
{ {
_interaction = interaction;
_userRepository = userRepository;
_ssoConfigRepository = ssoConfigRepository;
_clientStore = clientStore; _clientStore = clientStore;
_interaction = interaction;
_logger = logger; _logger = logger;
_ssoConfigRepository = ssoConfigRepository;
_userRepository = userRepository;
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> Login(string returnUrl) public async Task<IActionResult> Login(string returnUrl)
{ {
var context = await _interaction.GetAuthorizationContextAsync(returnUrl); var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context.Parameters.AllKeys.Contains("domain_hint") &&
!string.IsNullOrWhiteSpace(context.Parameters["domain_hint"])) var domainHint = context.Parameters.AllKeys.Contains("domain_hint") ?
context.Parameters["domain_hint"] : null;
if (string.IsNullOrWhiteSpace(domainHint))
{ {
return RedirectToAction(nameof(ExternalChallenge), throw new Exception("No domain_hint provided");
new { organizationIdentifier = context.Parameters["domain_hint"], returnUrl = returnUrl });
} }
else
var userIdentifier = context.Parameters.AllKeys.Contains("user_identifier") ?
context.Parameters["user_identifier"] : null;
return RedirectToAction(nameof(ExternalChallenge), new
{ {
throw new Exception("No domain_hint provided."); organizationIdentifier = domainHint,
} returnUrl,
userIdentifier
});
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> ExternalChallenge(string organizationIdentifier, string returnUrl) public async Task<IActionResult> ExternalChallenge(string organizationIdentifier, string returnUrl,
string userIdentifier)
{ {
if (string.IsNullOrWhiteSpace(organizationIdentifier)) if (string.IsNullOrWhiteSpace(organizationIdentifier))
{ {
@ -82,6 +95,11 @@ namespace Bit.Identity.Controllers
}, },
}; };
if (!string.IsNullOrWhiteSpace(userIdentifier))
{
props.Items.Add("user_identifier", userIdentifier);
}
return Challenge(props, scheme); return Challenge(props, scheme);
} }

View File

@ -100,6 +100,7 @@ namespace Bit.Identity
{ {
// Pass domain_hint onto the sso idp // Pass domain_hint onto the sso idp
context.ProtocolMessage.DomainHint = context.Properties.Items["domain_hint"]; context.ProtocolMessage.DomainHint = context.Properties.Items["domain_hint"];
context.ProtocolMessage.SessionState = context.Properties.Items["user_identifier"];
return Task.FromResult(0); return Task.FromResult(0);
} }
}; };

View File

@ -18,6 +18,7 @@ SELECT
O.[Seats], O.[Seats],
O.[MaxCollections], O.[MaxCollections],
O.[MaxStorageGb], O.[MaxStorageGb],
O.[Identifier],
OU.[Key], OU.[Key],
OU.[Status], OU.[Status],
OU.[Type], OU.[Type],

View File

@ -1,6 +1,3 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Bit.Api.Controllers; using Bit.Api.Controllers;
using Bit.Core; using Bit.Core;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -12,21 +9,26 @@ using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using NSubstitute; using NSubstitute;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Xunit; using Xunit;
namespace Bit.Api.Test.Controllers namespace Bit.Api.Test.Controllers
{ {
public class AccountsControllerTests : IDisposable public class AccountsControllerTests : IDisposable
{ {
private readonly AccountsController _sut;
private readonly IUserService _userService; private readonly AccountsController _sut;
private readonly IUserRepository _userRepository; private readonly GlobalSettings _globalSettings;
private readonly ICipherRepository _cipherRepository; private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository; private readonly IFolderRepository _folderRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService; private readonly IPaymentService _paymentService;
private readonly GlobalSettings _globalSettings; private readonly ISsoUserRepository _ssoUserRepository;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
public AccountsControllerTests() public AccountsControllerTests()
{ {
@ -34,17 +36,20 @@ namespace Bit.Api.Test.Controllers
_userRepository = Substitute.For<IUserRepository>(); _userRepository = Substitute.For<IUserRepository>();
_cipherRepository = Substitute.For<ICipherRepository>(); _cipherRepository = Substitute.For<ICipherRepository>();
_folderRepository = Substitute.For<IFolderRepository>(); _folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>(); _organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_paymentService = Substitute.For<IPaymentService>(); _paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings(); _globalSettings = new GlobalSettings();
_sut = new AccountsController( _sut = new AccountsController(
_userService, _globalSettings,
_userRepository,
_cipherRepository, _cipherRepository,
_folderRepository, _folderRepository,
_organizationService,
_organizationUserRepository, _organizationUserRepository,
_paymentService, _paymentService,
_globalSettings _ssoUserRepository,
_userRepository,
_userService
); );
} }

View File

@ -33,13 +33,14 @@ namespace Bit.Core.Test.Services
var paymentService = Substitute.For<IPaymentService>(); var paymentService = Substitute.For<IPaymentService>();
var policyRepo = Substitute.For<IPolicyRepository>(); var policyRepo = Substitute.For<IPolicyRepository>();
var ssoConfigRepo = Substitute.For<ISsoConfigRepository>(); var ssoConfigRepo = Substitute.For<ISsoConfigRepository>();
var ssoUserRepo = Substitute.For<ISsoUserRepository>();
var referenceEventService = Substitute.For<IReferenceEventService>(); var referenceEventService = Substitute.For<IReferenceEventService>();
var globalSettings = Substitute.For<GlobalSettings>(); var globalSettings = Substitute.For<GlobalSettings>();
var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo,
groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo, groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo,
licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo, licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo,
ssoConfigRepo, referenceEventService, globalSettings); ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings);
var id = Guid.NewGuid(); var id = Guid.NewGuid();
var userId = Guid.NewGuid(); var userId = Guid.NewGuid();
@ -93,13 +94,14 @@ namespace Bit.Core.Test.Services
var paymentService = Substitute.For<IPaymentService>(); var paymentService = Substitute.For<IPaymentService>();
var policyRepo = Substitute.For<IPolicyRepository>(); var policyRepo = Substitute.For<IPolicyRepository>();
var ssoConfigRepo = Substitute.For<ISsoConfigRepository>(); var ssoConfigRepo = Substitute.For<ISsoConfigRepository>();
var ssoUserRepo = Substitute.For<ISsoUserRepository>();
var referenceEventService = Substitute.For<IReferenceEventService>(); var referenceEventService = Substitute.For<IReferenceEventService>();
var globalSettings = Substitute.For<GlobalSettings>(); var globalSettings = Substitute.For<GlobalSettings>();
var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo,
groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo, groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo,
licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo, licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo,
ssoConfigRepo, referenceEventService, globalSettings); ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings);
var id = Guid.NewGuid(); var id = Guid.NewGuid();
var userId = Guid.NewGuid(); var userId = Guid.NewGuid();

View File

@ -0,0 +1,39 @@
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView')
BEGIN
DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView];
END
GO
CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView]
AS
SELECT
OU.[UserId],
OU.[OrganizationId],
O.[Name],
O.[Enabled],
O.[UsePolicies],
O.[UseSso],
O.[UseGroups],
O.[UseDirectory],
O.[UseEvents],
O.[UseTotp],
O.[Use2fa],
O.[UseApi],
O.[SelfHost],
O.[UsersGetPremium],
O.[Seats],
O.[MaxCollections],
O.[MaxStorageGb],
O.[Identifier],
OU.[Key],
OU.[Status],
OU.[Type],
SU.[ExternalId] SsoExternalId
FROM
[dbo].[OrganizationUser] OU
INNER JOIN
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
LEFT JOIN
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
GO