using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Identity; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Extensions; namespace Umbraco.Core.Persistence.Repositories.Implement { // TODO: We should update this to support both users and members. It means we would remove referential integrity from users // and the user/member key would be a GUID (we also need to add a GUID to users) internal class ExternalLoginRepository : EntityRepositoryBase, IExternalLoginRepository { public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } public void DeleteUserLogins(int memberId) { Database.Delete("WHERE userId=@userId", new { userId = memberId }); } public void Save(int userId, IEnumerable logins) { var sql = Sql() .Select() .From() .Where(x => x.UserId == userId) .ForUpdate(); // deduplicate the logins logins = logins.DistinctBy(x => x.ProviderKey + x.LoginProvider).ToList(); var toUpdate = new Dictionary(); var toDelete = new List(); var toInsert = new List(logins); var existingLogins = Database.Query(sql).OrderByDescending(x => x.CreateDate).ToList(); // used to track duplicates so they can be removed var keys = new HashSet<(string, string)>(); foreach (var existing in existingLogins) { if (!keys.Add((existing.ProviderKey, existing.LoginProvider))) { // if it already exists we need to remove this one toDelete.Add(existing.Id); } else { var found = logins.FirstOrDefault(x => x.LoginProvider.Equals(existing.LoginProvider, StringComparison.InvariantCultureIgnoreCase) && x.ProviderKey.Equals(existing.ProviderKey, StringComparison.InvariantCultureIgnoreCase)); if (found != null) { toUpdate.Add(existing.Id, found); // if it's an update then it's not an insert toInsert.RemoveAll(x => x.ProviderKey == found.ProviderKey && x.LoginProvider == found.LoginProvider); } else { toDelete.Add(existing.Id); } } } // do the deletes, updates and inserts if (toDelete.Count > 0) Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute(); foreach (var u in toUpdate) Database.Update(ExternalLoginFactory.BuildDto(userId, u.Value, u.Key)); Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userId, i))); } protected override IIdentityUserLogin PerformGet(int id) { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { id = id }); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); if (dto == null) return null; var entity = ExternalLoginFactory.BuildEntity(dto); // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Any()) { return PerformGetAllOnIds(ids); } var sql = GetBaseQuery(false).OrderByDescending(x => x.CreateDate); return ConvertFromDtos(Database.Fetch(sql)) .ToArray();// we don't want to re-iterate again! } private IEnumerable PerformGetAllOnIds(params int[] ids) { if (ids.Any() == false) yield break; foreach (var id in ids) { yield return Get(id); } } private IEnumerable ConvertFromDtos(IEnumerable dtos) { foreach (var entity in dtos.Select(ExternalLoginFactory.BuildEntity)) { // reset dirty initial properties (U4-1946) ((BeingDirtyBase)entity).ResetDirtyProperties(false); yield return entity; } } protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); var dtos = Database.Fetch(sql); foreach (var dto in dtos) { yield return ExternalLoginFactory.BuildEntity(dto); } } protected override Sql GetBaseQuery(bool isCount) { var sql = Sql(); if (isCount) sql.SelectCount(); else sql.SelectAll(); sql.From(); return sql; } protected override string GetBaseWhereClause() { return "umbracoExternalLogin.id = @id"; } protected override IEnumerable GetDeleteClauses() { var list = new List { "DELETE FROM umbracoExternalLogin WHERE id = @id" }; return list; } protected override Guid NodeObjectTypeId { get { throw new NotImplementedException(); } } protected override void PersistNewItem(IIdentityUserLogin entity) { entity.AddingEntity(); var dto = ExternalLoginFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); entity.Id = id; entity.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IIdentityUserLogin entity) { entity.UpdatingEntity(); var dto = ExternalLoginFactory.BuildDto(entity); Database.Update(dto); entity.ResetDirtyProperties(); } } }