| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Collections.Generic; |
| | | 6 | | using System.DirectoryServices.Protocols; |
| | | 7 | | using System.Linq; |
| | | 8 | | using System.Text; |
| | | 9 | | using System.Threading.Tasks; |
| | | 10 | | using CoreWCF.IdentityModel.Claims; |
| | | 11 | | using Microsoft.Extensions.Caching.Memory; |
| | | 12 | | |
| | | 13 | | namespace CoreWCF.Security |
| | | 14 | | { |
| | | 15 | | internal static class LdapAdapter |
| | | 16 | | { |
| | | 17 | | public static async Task<List<Claim>> RetrieveClaimsAsync(LdapSettings settings, string originalUserName) |
| | | 18 | | { |
| | 0 | 19 | | var upnIndex = originalUserName.IndexOf('@'); |
| | 0 | 20 | | var userAccountName = ""; |
| | 0 | 21 | | if (upnIndex == -1) |
| | | 22 | | { |
| | 0 | 23 | | int domainIndex = originalUserName.IndexOf("\\"); |
| | 0 | 24 | | if (domainIndex == -1) |
| | 0 | 25 | | return null; |
| | 0 | 26 | | userAccountName = originalUserName.Substring(domainIndex + 1); |
| | | 27 | | } |
| | | 28 | | else |
| | | 29 | | { |
| | 0 | 30 | | userAccountName = originalUserName.Substring(0, upnIndex); |
| | | 31 | | } |
| | | 32 | | |
| | 0 | 33 | | List<Claim> roleClaims = new List<IdentityModel.Claims.Claim>(); |
| | | 34 | | |
| | 0 | 35 | | if (settings.ClaimsCache.TryGetValue<IEnumerable<string>>(originalUserName, out var cachedClaims)) |
| | | 36 | | { |
| | 0 | 37 | | foreach (var claim in cachedClaims) |
| | | 38 | | { |
| | 0 | 39 | | roleClaims.Add(new Claim(ClaimTypes.Role, claim, Rights.Identity)); |
| | | 40 | | } |
| | | 41 | | |
| | 0 | 42 | | return roleClaims; |
| | | 43 | | } |
| | | 44 | | |
| | 0 | 45 | | var distinguishedName = settings.Domain.Split('.').Select(name => $"dc={name}").Aggregate((a, b) => $"{a},{b |
| | 0 | 46 | | var retrievedClaims = new List<string>(); |
| | 0 | 47 | | if (!string.IsNullOrEmpty(settings.OrgUnit)) |
| | | 48 | | { |
| | 0 | 49 | | distinguishedName = "OU=" + settings.OrgUnit + "," + distinguishedName; |
| | | 50 | | } |
| | | 51 | | |
| | 0 | 52 | | var genericFilter = $"(&(objectClass=user)(sAMAccountName={userAccountName}))"; // This is using ldap search |
| | 0 | 53 | | var upnFilter = $"(&(objectClass=user)(userPrincipalName={originalUserName}))"; |
| | 0 | 54 | | var genericSearchRequest = new SearchRequest(distinguishedName, genericFilter, SearchScope.Subtree, null); |
| | 0 | 55 | | var upnSearchRequest = new SearchRequest(distinguishedName, upnFilter, SearchScope.Subtree, null); |
| | 0 | 56 | | SearchResponse searchResponse = null; |
| | | 57 | | try |
| | | 58 | | { |
| | 0 | 59 | | if (upnIndex > 0) |
| | | 60 | | { |
| | 0 | 61 | | searchResponse = (SearchResponse)await Task<DirectoryResponse>.Factory.FromAsync( |
| | 0 | 62 | | settings.LdapConnection.BeginSendRequest, settings.LdapConnection.EndSendRequest, |
| | 0 | 63 | | upnSearchRequest, PartialResultProcessing.NoPartialResultSupport, null); |
| | 0 | 64 | | if(searchResponse !=null && searchResponse.Entries != null && searchResponse.Entries.Count > 1) |
| | | 65 | | { |
| | 0 | 66 | | throw new Exception(SR.DuplicateUPN); //resource |
| | | 67 | | } |
| | | 68 | | } |
| | 0 | 69 | | if (searchResponse == null || searchResponse.Entries == null || searchResponse.Entries.Count == 0) |
| | | 70 | | { |
| | 0 | 71 | | searchResponse = (SearchResponse)await Task<DirectoryResponse>.Factory.FromAsync( |
| | 0 | 72 | | settings.LdapConnection.BeginSendRequest, settings.LdapConnection.EndSendRequest, |
| | 0 | 73 | | genericSearchRequest, PartialResultProcessing.NoPartialResultSupport, null); |
| | | 74 | | } |
| | 0 | 75 | | } |
| | 0 | 76 | | catch (Exception ex) |
| | | 77 | | { |
| | | 78 | | |
| | 0 | 79 | | if (searchResponse?.ErrorMessage != null) |
| | | 80 | | { |
| | 0 | 81 | | throw new Exception(searchResponse.ErrorMessage); |
| | | 82 | | } |
| | | 83 | | else |
| | | 84 | | { |
| | 0 | 85 | | throw ex; |
| | | 86 | | } |
| | | 87 | | } |
| | | 88 | | |
| | 0 | 89 | | if (searchResponse.Entries.Count > 0) |
| | | 90 | | { |
| | 0 | 91 | | var userFound = searchResponse.Entries[0]; //Get the object that was found on ldap |
| | 0 | 92 | | var memberof = userFound.Attributes["memberof"]; // You can access ldap Attributes with Attributes prope |
| | | 93 | | |
| | 0 | 94 | | foreach (var group in memberof) |
| | | 95 | | { |
| | | 96 | | // Example distinguished name: CN=TestGroup,DC=KERB,DC=local |
| | 0 | 97 | | var groupDN = $"{Encoding.UTF8.GetString((byte[])group)}"; |
| | 0 | 98 | | var groupCN = groupDN.Split(',')[0].Substring("CN=".Length); |
| | 0 | 99 | | retrievedClaims.Add(groupCN); |
| | | 100 | | } |
| | | 101 | | |
| | 0 | 102 | | var entrySize = originalUserName.Length * 2; //Approximate the size of stored key in memory cache. |
| | 0 | 103 | | foreach (var claim in retrievedClaims) |
| | | 104 | | { |
| | 0 | 105 | | roleClaims.Add(new Claim(ClaimTypes.Role, claim, Rights.Identity)); |
| | 0 | 106 | | entrySize += claim.Length * 2; //Approximate the size of stored value in memory cache. |
| | | 107 | | } |
| | | 108 | | |
| | 0 | 109 | | settings.ClaimsCache.Set(originalUserName, |
| | 0 | 110 | | retrievedClaims, |
| | 0 | 111 | | new MemoryCacheEntryOptions() |
| | 0 | 112 | | .SetSize(entrySize) |
| | 0 | 113 | | .SetSlidingExpiration(settings.ClaimsCacheSlidingExpiration) |
| | 0 | 114 | | .SetAbsoluteExpiration(settings.ClaimsCacheAbsoluteExpiration)); |
| | | 115 | | } |
| | 0 | 116 | | return roleClaims; |
| | 0 | 117 | | } |
| | | 118 | | } |
| | | 119 | | } |