Skip to content

Commit e76f466

Browse files
authored
Merge pull request #156 from ucdavis/VPR-137-ldap-session-options
VPR-137 fix(ldap): enforce server certificate validation on LDAPS binds
2 parents c54c4b5 + 5ea5a94 commit e76f466

5 files changed

Lines changed: 88 additions & 27 deletions

File tree

web/Areas/RAPS/Services/OuGroupService.cs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -443,15 +443,29 @@ private async Task<List<TblRoleMember>> GetAllRoleMembers(int groupId, bool filt
443443
/// <returns></returns>
444444
private static GroupMember CreateGroupMember(TblRoleMember roleMember)
445445
{
446+
VwAaudUser? user = roleMember.AaudUser;
447+
if (user == null)
448+
{
449+
LogManager.GetCurrentClassLogger().Warn(
450+
"Role member {MemberId} in role {RoleId} has no AaudUser record.",
451+
roleMember.MemberId,
452+
roleMember.RoleId);
453+
return new()
454+
{
455+
MemberId = roleMember.MemberId,
456+
Roles = new List<GroupMemberRole>()
457+
};
458+
}
459+
446460
return new()
447461
{
448462
MemberId = roleMember.MemberId,
449-
LoginId = roleMember.AaudUser.LoginId,
450-
MailId = roleMember.AaudUser.MailId,
451-
DisplayFirstName = roleMember.AaudUser.DisplayFirstName,
452-
DisplayLastName = roleMember.AaudUser.DisplayLastName,
453-
DisplayName = roleMember.AaudUser.DisplayLastName + ", " + roleMember.AaudUser.DisplayFirstName,
454-
Current = roleMember.AaudUser.Current,
463+
LoginId = user.LoginId ?? string.Empty,
464+
MailId = user.MailId ?? string.Empty,
465+
DisplayFirstName = user.DisplayFirstName,
466+
DisplayLastName = user.DisplayLastName,
467+
DisplayName = user.DisplayLastName + ", " + user.DisplayFirstName,
468+
Current = user.Current,
455469
Roles = new List<GroupMemberRole>()
456470
};
457471
}
@@ -463,16 +477,25 @@ private static GroupMember CreateGroupMember(TblRoleMember roleMember)
463477
/// <returns></returns>
464478
private static GroupMemberRole CreateGroupMemberRole(TblRoleMember roleMember)
465479
{
480+
TblRole? role = roleMember.Role;
481+
if (role == null)
482+
{
483+
LogManager.GetCurrentClassLogger().Warn(
484+
"Role member {MemberId} references missing role {RoleId}.",
485+
roleMember.MemberId,
486+
roleMember.RoleId);
487+
}
488+
466489
return new()
467490
{
468491
RoleId = roleMember.RoleId,
469-
Role = roleMember.Role.FriendlyName,
492+
Role = role?.FriendlyName ?? string.Empty,
470493
AddDate = roleMember.AddDate != null ? DateOnly.FromDateTime((System.DateTime)roleMember.AddDate) : null,
471494
StartDate = roleMember.StartDate != null ? DateOnly.FromDateTime((System.DateTime)roleMember.StartDate) : null,
472495
EndDate = roleMember.EndDate != null ? DateOnly.FromDateTime((System.DateTime)roleMember.EndDate) : null,
473496
ModBy = roleMember.ModBy,
474497
ModDate = roleMember.ModTime != null ? DateOnly.FromDateTime((System.DateTime)roleMember.ModTime) : null,
475-
ViewName = roleMember.Role.ViewName
498+
ViewName = role?.ViewName ?? string.Empty
476499
};
477500
}
478501

web/Areas/RAPS/Views/Groups/Members.cshtml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
<q-btn no-caps color="primary" padding="xs md" class="q-mr-md q-mb-sm" label="Sync Members" @@click="syncGroup()"></q-btn>
1313
<q-btn no-caps color="primary" padding="xs md" class="q-mr-md q-mb-sm" label="Edit Members" :href="'RoleMembers?roleId=' + group.groupRoleId"></q-btn>
1414
<q-table dense
15-
row-key="roleId"
15+
ref="membersTable"
16+
row-key="memberId"
1617
:rows="groupMembers.rows"
1718
:loading="groupMembers.loading"
1819
:columns="groupMembers.columns"
1920
:pagination="groupMembers.pagination">
21+
<template v-slot:loading>
22+
<q-linear-progress indeterminate color="primary" aria-label="Loading group members" />
23+
</template>
2024
<template v-slot:body-cell-active="props">
2125
<q-td>
2226
<q-icon size="sm" :name="props.row.current ? 'check' : 'close'" :color="props.row.current ? 'positive' : 'negative'"></q-icon>
@@ -70,7 +74,9 @@
7074
<template v-slot:body-cell-mod="props">
7175
<q-td>
7276
<span v-for="role in props.row.roles">
73-
{{fdate(role.modDate)}} ({{role.modBy}})
77+
<template v-if="role.modDate || role.modBy">
78+
{{fdate(role.modDate)}}<template v-if="role.modBy"> ({{role.modBy}})</template>
79+
</template>
7480
<br />
7581
</span>
7682
</q-td>
@@ -101,7 +107,7 @@
101107
{ name: "end", label: "End Date", field: "", align: "left", sortable: false },
102108
{ name: "mod", label: "Modified", field: "", align: "left", sortable: false }
103109
],
104-
pagination: { rowsPerPage: 0 }
110+
pagination: { rowsPerPage: 50 }
105111
})
106112
}
107113
},
@@ -128,6 +134,14 @@
128134
this.loadGroup()
129135
this.groupMembers.urlBase = "Groups/" + this.groupId + "/Members"
130136
this.groupMembers.load()
137+
this.$nextTick(() => {
138+
const scroll = this.$refs.membersTable?.$el?.querySelector(".q-table__middle")
139+
if (scroll) {
140+
scroll.setAttribute("tabindex", "0")
141+
scroll.setAttribute("role", "region")
142+
scroll.setAttribute("aria-label", "Group members table")
143+
}
144+
})
131145
}
132146
})
133147
</script>

web/Areas/RAPS/Views/Groups/Roles.cshtml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<h1>Roles for group {{group.name}}</h1>
55
<q-table
66
dense
7+
class="full-width"
8+
table-style="width:100%"
79
row-key="roleId"
810
:rows="groupRoles.rows"
911
:loading="groupRoles.loading"
@@ -29,6 +31,9 @@
2931
<h2>Add Roles</h2>
3032
<q-table
3133
dense
34+
wrap-cells
35+
class="full-width"
36+
table-style="width:100%"
3237
row-key="roleId"
3338
:rows="allRoles.rows"
3439
:loading="allRoles.loading"
@@ -76,10 +81,10 @@
7681
urlBase: "Roles?allInstances=true&Application=0",
7782
columns: [
7883
{ name: "links", label: "Members", field: "", align: "left", style: "max-width:75px;" },
79-
{ name: "instance", label: "Instance", field: "instance", align: "left", sortable: true },
80-
{ name: "role", label: "Role", field: "friendlyName", align: "left", sortable: true },
81-
{ name: "description", label: "Description", field: "description", align: "left", sortable: true, style: "max-width:400px;" },
82-
{ name: "add", label: "Add", field: "", align: "left" }
84+
{ name: "instance", label: "Instance", field: "instance", align: "left", sortable: true, style: "max-width:110px;overflow-wrap:anywhere;" },
85+
{ name: "role", label: "Role", field: "friendlyName", align: "left", sortable: true, style: "max-width:200px;overflow-wrap:anywhere;" },
86+
{ name: "description", label: "Description", field: "description", align: "left", sortable: true, style: "max-width:600px;overflow-wrap:anywhere;" },
87+
{ name: "add", label: "Add", field: "", align: "left", style: "max-width:90px;" }
8388
],
8489
pagination: { rowsPerPage: 0 },
8590
onLoad: function (data, vueApp) {

web/Classes/Utilities/ActiveDirectoryService.cs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static List<LdapGroup> GetGroups(string? name = null)
101101
var searchResults = SearchActiveDirectory(filter, Server.OU, ObjectType.Group);
102102
List<LdapGroup> groups = new();
103103

104-
foreach (SearchResultEntry e in searchResults.Entries)
104+
foreach (SearchResultEntry e in searchResults)
105105
{
106106
groups.Add(new LdapGroup(e));
107107
}
@@ -120,9 +120,9 @@ public static List<LdapGroup> GetGroups(string? name = null)
120120
string filter = string.Format("(&(objectClass=group)(distinguishedName={0}))", dn);
121121
var searchResults = SearchActiveDirectory(filter, Server.OU, ObjectType.Group);
122122
LdapGroup? group = null;
123-
if (searchResults.Entries.Count == 1)
123+
if (searchResults.Count == 1)
124124
{
125-
group = new LdapGroup(searchResults.Entries[0]);
125+
group = new LdapGroup(searchResults[0]);
126126
}
127127

128128
return group;
@@ -160,7 +160,7 @@ public static List<LdapUser> GetUsers(bool fromOu = false, string? name = null,
160160
var searchResults = SearchActiveDirectory(filter, fromOu ? Server.OU : Server.AD3, ObjectType.Person);
161161

162162
List<LdapUser> users = new();
163-
foreach (SearchResultEntry result in searchResults.Entries)
163+
foreach (SearchResultEntry result in searchResults)
164164
{
165165
users.Add(new LdapUser(result));
166166
}
@@ -180,7 +180,7 @@ public static List<LdapUser> GetUsers(bool fromOu = false, string? name = null,
180180
string.Format("(&(objectClass=user)(samAccountName={0}))", samAccountName),
181181
fromOu ? Server.OU : Server.AD3,
182182
ObjectType.Person);
183-
return searchResults.Entries.Count == 1 ? new LdapUser(searchResults.Entries[0]) : null;
183+
return searchResults.Count == 1 ? new LdapUser(searchResults[0]) : null;
184184
}
185185

186186
/// <summary>
@@ -196,11 +196,11 @@ public static List<LdapUser> GetGroupMembership(string groupDn)
196196
var ouSearchResults = SearchActiveDirectory(filter, Server.OU, ObjectType.Person);
197197
var ad3SearchResults = SearchActiveDirectory(filter, Server.AD3, ObjectType.Person);
198198

199-
foreach (SearchResultEntry result in ouSearchResults.Entries)
199+
foreach (SearchResultEntry result in ouSearchResults)
200200
{
201201
users.Add(new LdapUser(result));
202202
}
203-
foreach (SearchResultEntry result in ad3SearchResults.Entries)
203+
foreach (SearchResultEntry result in ad3SearchResults)
204204
{
205205
users.Add(new LdapUser(result));
206206
}
@@ -277,7 +277,7 @@ private static List<LdapUser> SortUsers(List<LdapUser> users)
277277
/// <param name="ou"></param>
278278
/// <param name="groupProperties"></param>
279279
/// <returns></returns>
280-
private static SearchResponse SearchActiveDirectory(string searchFilter, Server server, ObjectType objectType)
280+
private static List<SearchResultEntry> SearchActiveDirectory(string searchFilter, Server server, ObjectType objectType)
281281
{
282282
var ldapIdentifier = server == Server.OU
283283
? new LdapDirectoryIdentifier(_ouServer, port)
@@ -292,12 +292,32 @@ private static SearchResponse SearchActiveDirectory(string searchFilter, Server
292292
using var lc = new LdapConnection(ldapIdentifier, new System.Net.NetworkCredential(_username, cred, "ad3.ucdavis.edu"));
293293
lc.SessionOptions.ProtocolVersion = 3;
294294
lc.SessionOptions.SecureSocketLayer = true;
295-
lc.SessionOptions.VerifyServerCertificate = (connection, certificate) => true;
296295
lc.Bind();
297296

298297
var searchRequest = new SearchRequest(searchStart, searchFilter, SearchScope.Subtree, props);
299-
var response = (SearchResponse)lc.SendRequest(searchRequest);
300-
return response;
298+
// AD caps a single search at 1000 entries; use paging to retrieve all results.
299+
var pageRequest = new PageResultRequestControl(1000);
300+
searchRequest.Controls.Add(pageRequest);
301+
302+
var entries = new List<SearchResultEntry>();
303+
while (true)
304+
{
305+
var response = (SearchResponse)lc.SendRequest(searchRequest);
306+
foreach (SearchResultEntry entry in response.Entries)
307+
{
308+
entries.Add(entry);
309+
}
310+
311+
var pageResponse = response.Controls
312+
.OfType<PageResultResponseControl>()
313+
.FirstOrDefault();
314+
if (pageResponse == null || pageResponse.Cookie.Length == 0)
315+
{
316+
break;
317+
}
318+
pageRequest.Cookie = pageResponse.Cookie;
319+
}
320+
return entries;
301321
}
302322
}
303323
}

web/Classes/Utilities/LdapService.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ private static SearchResponse SearchLdap(string searchFilter)
2929
AuthType.Basic);
3030
lc.SessionOptions.ProtocolVersion = 3;
3131
lc.SessionOptions.SecureSocketLayer = true;
32-
lc.SessionOptions.VerifyServerCertificate = (connection, certificate) => true;
3332
lc.Bind();
3433

3534
var searchRequest = new SearchRequest(_ldapStart, searchFilter, SearchScope.Subtree, personAttributes);

0 commit comments

Comments
 (0)