Update user using DirectoryService - directoryservices

So I managed to get some code rolling for updating the AD from an external sourced. However, I'm a bit confused about how it works.
I have a person with sAMAccount xxxx existing in different OUs.
Now, I only want to update the info in a specific OU, so I put that in my LDAP path. Still, it seem that the info has been updated in different OU's as well?
Could that be possible? Is it because there's only one "Person" object, or do the "GetDirectoryEntry()" not put me where I thought in the tree? Or.. am I only imagine and the weird things I see is becausde of something else.
Some code
DirectoryEntry entry = new DirectoryEntry(LDAP://my.path/ou=myou, dc=my, dc=path);
entry.Username = myUser
entry.Password = myPass
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = #"(&(objectClass=Person)(SamAccountname=" + person.id + "))";
SearchResult result = searcher.FindOne();
try
{
DirectoryEntry user = result.GetDirectoryEntry();
user.Properties["displayName"].Value = person.DisplayName;
user.Properties["givenName"].Value = person.Firstname;
user.CommitChanges();
user.Close();
}
catch (DirectoryServicesCOMException ex)
EDIT: It did update the Person object in all the OU's. So either the Person object is one and the same in the whole AD, whick makes my attempt to update them in only the specific OU pointless, or does the "result.GetDirectoryEntry" ignore the fact that I thought I was working only in my specific OU declared in my LDAP path.

Functionality confirmed, still wonder why I needed a specific test-ou since it's still the same users. Anyway, here we go!

Related

How to change the database for the meteor users collection?

I Basically have this, which is working fine:
Ideas = new Mongo.Collection('ideas', {_driver: companyDb});
CompanyDb above is defined as:
companyDbString = 'mongodb://127.0.0.1:27017/demo';
companyDb = new MongoInternals.RemoteCollectionDriver(companyDbString);
However, how would i change the driver for the Meteor users collection. I've tried this:
Meteor.users = new Mongo.Collection('users', {_driver: companyDb});
But it gives the expected 'Error: There is already a collection named "users"' error.
I know this sounds like a strange use case, but 'companyDb' changes as the subdomain changes in my application. So I basically need a separate mongo database per sub domain
Try this
AnotherUsersCollection = new Mongo.Collection(“users”, { _driver: companyDb });
or
var remote = 'mongoURL';
database = new MongoInternals.RemoteCollectionDriver(remote);
You can perform database operations with your database object, like so.
database.mongo.findOne('users', "selector_object")
An example might be
database.mongo.findOne('users', {emails : {$elemMatch : {address : "email_string"} } } )
courtsey Dan crescimano
https://forums.meteor.com/t/remote-database-has-the-same-collection-name/7157/3

Need to setup SqlDependency per-user

System Scope
I have a database with a lot of users (over 50,000). At any time there may be 100-200 people logged in and actively using the system. The system is ASP.NET MVC 4, with Sql Server 2008 backend. For data access we are using Dapper.
Requirements
I am trying to build a notification component that has the following attributes:
When a new record is created in the [dbo.Assignment] table (with OwnerId = [Currently logged in user]), I need to update the Cache inside of an asp.net application.
I don't want to receive any notifications for users who are not actively online, as this would be a massive waste of resources)
Specific Questions:
Should I be using SqlDependency, SqlCacheDependency, or SqlNotification?
Assuming that we are using SqlDependency, how would I remove the Dependency.OnChange handler when user has logged out.
Any code samples would be much appreciated, as this has consumed the whole part of my day trying to figure it out.
Here is the current code
public IList<Notification> GetNotifications(string userName)
{
Cache o = HttpContext.Current.Cache;
if (o["Notifications_" + userName] == null)
{
var notifications = new List<Notification>();
using (var cn = new SqlConnection(getSQLString()))
{
using (var cmd = cn.CreateCommand())
{
var parameter = new SqlParameter("Employee_Cd", SqlDbType.Char, 30) { Value = userName };
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Notifications.Assignments";
cmd.Parameters.Add(parameter);
cmd.Notification = null;
var dependency = new SqlCacheDependency(cmd);
cn.Open();
using (var dr = cmd.ExecuteReader())
{
// this is where you build your cache
while (dr.Read())
{
var obj = new Notification();
obj.Name = dr["Name"].ToString();
notifications.Add(obj);
}
dr.Close();
}
HttpContext.Current.Cache.Insert("Notifications_" + userName,
notifications,
dependency,
DateTime.Now.AddDays(1D),
Cache.NoSlidingExpiration);
}
}
}
return (List<Notification>) o["Notifications_" + userName];
}
Note: I am not experienced with using SqlDependencies, as I have never really needed to use them until today. It's very possible that I am overlooking something important.
I didn’t really use any of these techniques but here are some alternatives that you can create yourself that will do the job just as good.
If you need to update cache every time new record is inserted into dbo.Assignment table why not create OnInserted event in your data access layer that will notify the cache object to refresh?
Another thing you can to is to create INSERT trigger in Assignemt table and another table that can look like this dbo.Cache (LastUpdate datetime). Trigger will insert value into Cache table and your application cache can ping this table like every X mins or X seconds to see if cache update is required.
If you need to refresh the cache immediately after record is inserted triggers might be an overkill because you’d have to ping Cache table probably every second but if you have 200 online users at a time that probably won’t make much of a difference in DB performance.
There is a lot of work if you want to implement these for a lot of tables but since this is only one table this might turn out to be faster way than implementing built in cache mechanisms.

Retrieve additional user properties from ActiveDirectoryMembershipProvider

The ActiveDirectoryMembershipProvider in ASP.NET returns users as instances of MembershipUser. This class only returns two of the properties defined for the given user in AD: email and username. I need to get access to additional properties, specifically "DisplayName", as I need to show full names in a dropdown in a web form.
The only way I can find to do this, is via a separate connection to AD, along the lines of what is described here: How can I convert from a SID to an account name in C#. This seems like a cumbersome and inefficient solution. I would like to do something like membershipProvider.GetUserProperty(username, propertyName), but that's not available.
Are there any nice solutions that people know of?
Based on feedback from my colleagues (thanks, Eirik!), #KennyZ's comment and lots of Googl'ing, I have found that this is the best/only way to do it. For reference, and other people seeing this question, here is some useful code for getting the AD settings out of web.config+connectionStrings.config, and using that data to query AD for a given user's Display Name:
var membershipSection = (MembershipSection)WebConfigurationManager.GetSection("system.web/membership");
var providerSettings = membershipSection.Providers["ActiveDirectoryMembershipProvider"];
var connectionStringName = providerSettings.Parameters["connectionStringName"];
var adUser = providerSettings.Parameters["connectionUsername"];
var adPassword = providerSettings.Parameters["connectionPassword"];
var adConnection = WebConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
var adReference = new DirectoryEntry(adConnection, adUser, adPassword);
var search = new DirectorySearcher(adReference) {Filter = string.Format("(mail={0})", username)};
search.PropertiesToLoad.Add("displayName");
SearchResult result = search.FindOne();
if (result != null)
{
var resultCollection = result.Properties["displayName"];
if (resultCollection.Count > 0)
{
var displayName = resultCollection[0].ToString();
...
}
}
Note: This assumes that I am using userPrincipalName as the attributeMapUsername in web.config, as that maps to the user's email address.

LDAP user attribute request returns unusual results

I'm struggling to return user details from AD using LDAP, after i have authenticated that the user exists.
I am using a simple auth method as follows:
Function AuthenticateUser(path As String, user As String, pass As String) As Boolean
Dim de As New DirectoryEntry(path, user, pass, AuthenticationTypes.Secure)
Try
Dim ds As DirectorySearcher = New DirectorySearcher(de)
Dim result As SearchResult = ds.FindOne()
If result Is Nothing Then Return False
'>>DEBUG OUTPUTS ONLY:
displayName.Text = result.GetDirectoryEntry().Properties.Item("distinguishedName").Value
displayName.Text += result.GetDirectoryEntry().Properties("name").Value
Return True
Catch
Return False
End Try
End Function
the problem is that "distinguishedName" returns "DC=our-domain,DC=co,DC=uk"
and "name" returns just "our-domain", not the name of the user that has just been auth'ed
Note: the displayName.text outputs are purely for debug purposes
I have tried various combos of requests but nothing seems to return USER details.
ETA: to the security police: this is all within a https connection, I'm not sending passwords about in plain text!
1. Dim de As New DirectoryEntry(path, user, pass, AuthenticationTypes.Secure)
2. Try
3. Dim ds As DirectorySearcher = New DirectorySearcher(de)
4. Dim result As SearchResult = ds.FindOne()
Line 1 is basically creating a DirectoryEntry element, that refers to the object at path. The only purpose that the username and password parameters serve is to authorise access to whatever entity path refers to.
As you currently have things, you're binding to the domain, not to the user (but you're authorised to connect to the domain as that user).
You then, in line 3, create a DirectorySearcher. But the constructor you're using just says to root the search at de (which as we've established, is just the domain). You've not done anything yet to search for that particular user within the domain - they could be connecting to perform almost any kind of search imaginable.
What you might want to do is look at the overload of DirectorySearcher that accepts a filter parameter - and provide a filter parameter that restricts the search to just the user. I don't know what form your user parameter is in - if it is, say, in the form of a user principal name (user#domain), you might try specifying a filter of:
Dim ds As DirectorySearcher = New DirectorySearcher(de,"(userPrincipalName=" + user + ")")
If you have just a username, you'd want to search against sAMAccountName. If you have an older style domain name (domain\user), then usually you want to split that on \, discard the domain name, and still search on sAMAccountName.
Some (but not too much!) help on constructing the filter parameters is found in the Filter property documentation.

Is asp.net caching my sql results?

I have the following method in an App_Code/Globals.cs file:
public static XmlDataSource getXmlSourceFromOrgid(int orgid)
{
XmlDataSource xds = new XmlDataSource();
var ctx = new SensusDataContext();
SqlConnection c = new SqlConnection(ctx.Connection.ConnectionString);
c.Open();
SqlCommand cmd = new SqlCommand(String.Format("select orgid, tekst, dbo.GetOrgTreeXML({0}) as Subtree from tblOrg where OrgID = {0}", orgid), c);
var rdr = cmd.ExecuteReader();
rdr.Read();
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<node orgid=\"{0}\" tekst=\"{1}\">",rdr.GetInt32(0),rdr.GetString(1));
sb.Append(rdr.GetString(2));
sb.Append("</node>");
xds.Data = sb.ToString();
xds.ID = "treedata";
rdr.Close();
c.Close();
return xds;
}
This gives me an XML-structure to use with the asp.net treeview control (I also use the CssFriendly extender to get nicer code)
My problem is that if I logon on my pc with a code that gives me access on a lower level in the tree hierarchy (it's an orgianization hierarchy), it somehow "remembers" what level i logon at. So when my coworker tests from her computer with another code, giving access to another place in the tree, she get's the same tree as me.
(The tree is supposed to show your own level and down.)
I have added a html-comment to show what orgid it passes to the function, and the orgid passed is correct. So either the treeview caches something serverside, or the sqlquery caches it's result somehow...
Any ideas?
Sql function:
ALTER function [dbo].[GetOrgTreeXML](#orgid int)
returns XML
begin RETURN
(select org.orgid as '#orgid',
org.tekst as '#tekst',
[dbo].GetOrgTreeXML(org.orgid)
from tblOrg org
where (#orgid is null and Eier is null) or Eier=#orgid
for XML PATH('NODE'), TYPE)
end
Extra code as requested:
int orgid = int.Parse(Session["org"].ToString());
string orgname = context.Orgs.Where(q => q.OrgID == orgid).First().Tekst;
debuglit.Text = String.Format("<!-- Id: {0} \n name: {1} -->", orgid, orgname);
var orgxml = Globals.getXmlSourceFromOrgid(orgid);
tvNavtree.DataSource = orgxml;
tvNavtree.DataBind();
Where "debuglit" is a asp:Literal in the aspx file.
EDIT:
I have narrowed it down. All functions returns correct values. It just doesn't bind to it. I suspect the CssFriendly adapter to have something to do with it.
I disabled the CssFriendly adapter and the problem persists...
Stepping through it in debug it's correct all the way, with the stepper standing on "tvNavtree.DataBind();" I can hover the pointer over the tvNavtree.Datasource and see that it actually has the correct data. So something must be faulting in the binding process...
I would normally suspect the issue is with orgid that is getting passed in to your method, but you say that you have checked to make sure the right code is being passed. Just to confirm, show us the code that assigns the value to that.
Additionally, there are a few problems with your code, SQL injection risk being one of them. orgid is an int, offering some protection, but if at some point orgid is changed to require characters by your organization, a developer may just change the data type to string, suddenly opening up the app to SQL injection. You should remove the String.Fotmat, and use a parameterized query instead.
I found the problem. The XmlDataSource component has a cache function, which by default is enabled. When I disabled this, everything works nicely.

Resources