-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathActive Directory Programming for Developers=Steve Evans;Note=Erxin.txt
454 lines (364 loc) · 13.8 KB
/
Active Directory Programming for Developers=Steve Evans;Note=Erxin.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
Active Directory Programming for Developers=Steve Evans;Note=Erxin
# introduction
- what is LDAP & Active Directory?
- introduction to LDAP
+ what is LDAP, light weight directory access protocol
+ enterprise address book
users and information, used for authenticate users
groups, group up users to control access resources
computers/servers, management
services, discovery
application data
- LDAP terminology
+ directory structure
LDAP://CN=user, OU=Employee,DC=company,DC=com
LDAP://ldap.company.com/OU=Groups,DC=company,DC=com
+ LDAP tool, active directory users and computers to browse LDAP information
the structure is like a tree, the leaf is user information
+ object types and attributes
user, first name, last name username, email, phone numbers
group, user name, group memebership
organization unit like a folder in the structure
- Introduction to Active directory
+ AD, active directory is a server of Microsoft, it contain in the domain controler
+ AD lightweight directory services (ADLDS) is another server for
applications
web authentication
testing
- LDAP programming with .net
+ ADSI, active directory services interface
+ .net 1.0, system.direcotryServices introduced, .net wrapper for ADSI any LDAP server, no strongly typed objects
+ .net 2.0, system.DirectoryServices.Protocols, skips ADSI, better performance /harder to use
+ .net 3.5, system.DirectoryServices.AccountManagement, AD/ADLDS only, user, group, computer objects only, strongly typed objects, ADO.net vs ORM
+ powershell, module activeDirectory very similar to S.DS.AccountManagement
# system.directory.services
- DirectoryEntry class, represents individual object in LDAP storage, can be any thing such as root of LDAP, or other type of object such as user
- net significant class is DirectorySearcher class, LDAP searchers
- SearchResult/SearchResultCollection
+ LDAP search result
+ read only must convert to DirectoryEntry for R/W
- binding to LDAP
//this will directly connect to the root
var Test = new DirectoryEntry(
"LDAP://ldap.itd.umich.edu",
null,
null,
AuthenticationTypes.Anonymous);
//prep AD
DirectoryEntry RootOU = new DirectoryEntry(
"LDAP://DC=demo,dc=local",
null, //username
null, //password
AuthenticationTypes.Secure);
//connect to special organization unit(OU)
DirectoryEntry SampleOU = new DirectoryEntry(
"LDAP://OU=sample,DC=demo,dc=local",
null, //username
null, //password
AuthenticationTypes.Secure);
// until directly query to entry, then will start to connect to the LDAP server
SampleOU.NativeGuid;
- creating objects
+ find the parent of a node in LDAP
+ use the child property to add a object
DirectoryEntry groupEnt = SampleOU.Children.Add("CN=TestGroup", "Group");
using(groupEnt)
{
//it's the username for legacy reason it named sAMAccountName
groupEnt.Properties["sAMAccountName"].Value = "TestGroup";
groupEnt.CommitChanges();
}
+ user name
* user principle name, like the [email protected]
* sAMAccountName pre-windows 2000, DEMO\UserName
userEnt.Properties["sAmAccountName"].Value = user.UserName;
userEnt.Properties["userPrincipalName"].Value = user.UserName+"@demo.local"
userEnt.Properties["givenName"].Value = user.FirstName;
userEnt.Properties["sn"].Value = user.LastName;
userEnt.CommitChanges();
//enable the user, by default the created user is disabled
int val = (int)userEnt.Properties["userAccountControl"].Value;
userEnt.Properties["userAccountControl"].Value = val &~ADS_UF_ACCOUNTDISABLE;
userEnt.CommmitChanges();
- find objects
DirectorySearcher searcher;
SearchResultCollection result;
//LDAP search expression is (attribute operators value)
//operators such as =
searcher.Filter = "(sAmAccountName=steve.evans)";
searcher.SearchRoot = SampleOU;
using(searcher)
{
results = searcher.FindAll();
if(result.Count == 1)
{
string path = results[0].Path;
user1 = results[0].GetDirectoryEntry();
}
}
the search result is readonly and the directory result is read and write capable
- advanced searches
+ use wildcard case matching
(as*A)
+ query a and operation for both condition
(&(sn=Evans)(givenName=Steve))
a query start with specific characters is efficient than wildcard in the front. if whildcard in the middle which is the mose expensive operation
+ the expression is like lisp language
- object properties
userEnt.Properties["sAmAccountName"].Value = user.UserName;
userEnt.CommitChanges();
var sn = Convert.ToString(userEnt.Properties["sAmAccountName"].Value);
there is a multiple values attribute property for a entry, such as a member property of a group
string userDN = user1.Properties["distinguishedName"].Value.ToString();
group1.Properties["member"].Add(userDN);
group1.CommitChanges();
ldapDirectoryEntry.Path will display LDAP connection value
- Set up users passwords, think the set password is a kind of store procedure, it's a little different in other property operation
userEnt.Invok("SetPassword", new object[]{Password});
- deleting user
result.GetDirectoryEntry().DeleteTree();
# S.DS.AccountManagement
- Classes in the namespace
+ PrincipleContext class, binding to directory
+ UserPrincipal class, individual AD user
+ GroupPrincipal class, individual AD group
+ Search,
PrincipalSearcher,
UserPrincipal.Methods(),
GroupPrincipal.Methods()
- Binding to AD
//directly connect to the root
DomainContext = new PrincipalContext(ContextType.Domain);
DomainContext = new PrincipalContext(ContextType.ApplicationDirectory);
//localmachine
DomainContext = new PrincipalContext(ContextType.Machine);
DomainContext = new PrincipalContext(ContextType.Domain,
null, //current computer domain
"OU=Sample,DC=demo,DC=local");
- Creating objects
+ create user by UserPrincipal class
var user = new UserPrincipal(
SampleOUContext,
user.UserName,
"password",
true
);
user.GivenName = "First name";
user.Surname = "Last name";
user.UserPrincipalName = "[email protected]";
using(user)
{
user.Save();
}
+ creating group
* group scope
domain local, have multiple domain
global, for all
universal
* group type
security, for assign permissions
distribution, don't need to assign any kinds of permissions
* code example
var group = new GroupPrincipal(
SampleOUContext,
"Group name"
);
group.GroupScope = GroupScope.Global;
using(group)
{
newGroup.Save();
}
- Finding objects
var user = UserPrincipal.FindbyIdentity(
SampleOUContext,
"name"
);
group = GroupPrincipal.FindByIdentity(
SampleOUContext,
"name"
);
get current user login information by
UserPrincipal.Current
there are several other kinds of method to get user information by the UserPrincipal class
UserPrincipal.FindByLoginTime
UserPrincipal.FindbyExpirationTime
UserPrincipal.FindByPasswordSetTime
UserPrincipal.FindbyBadPasswordAttempt
- Advanced searches
UserPrincipal userSearch = new UserPrincipal(SampleOUContext);
userSearch.Surname = "E*";
PrincipalSearcher searcher = new PrincipalSearcher();
searcher.QueryFiler = userSearch();
using(searcher)
{
PrincipalSearchResult<Principal> result = searcher.FindAll();
foreach(Principal result in result)
{
string name = result.Name;
}
}
- Object properties
user.Description
user.VocieTelephoneNumber
add user to group
group.Memebers.Add(user);
- Extending S.DS.AM Classes, for dealing with code with legacy or do something which is not supplied by the System.Directory Account Management
DirecotryEntry userentry = (DirecotryEntry)user.GetUnderlyingObject();
DirecotryEntry parent = userentry.Parent;
extending the user principal or group principal class, such as add properties
public class UserExtended:UserPrincipal
{
[DirectoryProperty("customProperty")]
public string CustomProperty
{
get
{
object [] result = this.ExtensionsGet("CustomProperty");
if (result != null)
{
return (string)result[0];
}
else
{
return null;
}
}
}
}
use the extend user class just like the user principal class
UserExtend user = new UserExtend(SampleOUContext);
user.Save();
- set user password
user.SetPassword(password);
- delete object
user.Delete();
# PowerShell AD Module
# Performance Optimization
- search optimization
+ set the search root where you begin to search
DirectorySearcher search = new DirectorySearcher();
search.SearchRoot = new DirectoryEntry("LDAP://OU=Sample,DC=demo,DC=local");
+ using search with index search, LDAP have same to indexes in sql
* adfind, tool to search ldap from command line
http://joeware.net/freetools/
adfind -default -f "(telephoneNumber=555-555)" -stats+Only
the parameter -stats+Only will display the query statistic information
- GUID binding, binding specify user by the GUID
var Guid = DirectoryEntryObject.Guid
rebind the same object by Guid
var user = new DirectoryEntry(string.Format("LDAP://<GUID={0}>", Guid.ToString("D")));
- Global catalog searches, is a read-only partial representation of all the domain in the forest.
active directory consist as a forest may compose with multiple domains
+ look for user from multiple domain, there is a readonly global catolog for search all the domain in the active directory
DirectoryEntry gc = new DirecotryEntry("GC:");
foreach(var root in gc.Children)
{
//get to the forest
gc = root;
}
DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchRoot = gc;
search.Filter = "(sAMAccountName=steve.evans)";
SearchResultCollection results = searcher.FindAll();
if(results.Count == 2)
{
string path = result[0].Properties["distringuishedName"][0]
DirectoryEntry dc = new DirectoryEntry();
dc.Path = string.Format("LDAP://{0}", path);
string username = dc.Properties["sAMAccuntName"].Value.ToString();
}
- Attribute Scoped Queries
filter active directory by attribute
DirectoryEntry group = new DirectoryEntry("LDAP://CN=Talent, OU=Mail,DC=demo,DC=local");
var searcher = new DirectorySearcher();
seracher.SearchRoot = group;
sercher.Filter="(&(objectClass=contact)(objectCategory=person)(mail=*))"
searcher.PropertiesToLoad.Add("mail");
searcher.SearchScope = SearchScope.Base;
//looks the member of the group
searcher.AttributeScopeQuery = "member";
using(SearcherResultCollection results = searcher.FindAll())
{
List<string> mail = new List<string>();
foreach(SearchResult result in results)
{
mail.Add(result.Properties["mail"][0].ToString());
}
}
- Connection Pooling, make sure use the same connection
DirectoryEntry connection = new DirectoryEntry("LDAP://DC=demo,DC=local");
using(connection)
{
//connection created now, and existing connection reuses
tmp = connection.NativeObject;
}
in the using scope if create a same DirectoryEntry with same parameters will actually only use the connection of the using block. if the parameter is different such as use different authentication then will create a new connection
# LDAP Authentication
- System.DirectoryServices Authentication
public bool Authenticate(string UserName, string Password)
{
const int ERROR_LOGO_FAILURE = -2147023570;
//every LDAP server have a concept of rootDSE, per LDAP spec, every user is able to read attributes off of it
DirecotryEntry root = new DirecotryEntry(
"LDAP://rootDSE",
UserName,
Password,
AuthenticationTypes.Secure);
using(root)
{
try
{
object tmp = root.NativeObject;
return true;
}
catch(System.Runtime.InteropService.COMException ex)
{
if(ex.ErrorCode != ERROR_LOGON_FAILURE)
{
throw;
}
else
{
return false;
}
}
}
}
- S.DS.Account Management
PrincipleContext adContext = new PrincipalContext(ContextType.Domain);
using(adContext)
{
return adContext.ValidateCredentials(UserName, Password);
}
- authenticate with asp.net
edit in the web.config
<connectionStrings>
<add name="ADService" connectionString="LDAP://demo.local"/>
</connectionString>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/login.aspx" timeout="2880"/>
</authentication>
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
<providers>
<clear/>
<add name="AspNetActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirecotryMembershipProvider, System.Web"
connectionStringName="ADService"
attributeMapUsername="sAMAccountName"
</providers>
</membership>
<authorization>
<deny user="?"/>
</authorization>
</system.web>
create login.aspx page and use default authorization user control
<asp:login></asp:login>
login to the default.aspx
<asp:LoginStatus/>
<asp:LoginName/>
get detail of login account
PrincipalContext context = new PrincipleContext(ContextType.Domain)
string username = HttpCOntext.Current.User.Identity.Name;
UserPrincipal user = UserPrincipal.FindByIdentity(context, username);
user.VoiceTelephoneNumber;