Update Active Directory without hardcoding username/password - vb.net

Currently, users log into a web application with their AD (active directory) credentials which are validated against the AD. Once inside the application, certain users will need to update the AD. When I hardcode a username/password, I am able to update the AD, however when I try to force the object to use the logon credentials or if I don't specify the username/password, it throws an error. Obviously due to security concerns, I do not want to hardcode credentials. Is there a solution for this?
Error - System.DirectoryServices.DirectoryServicesCOMException: An operations error occurred.
Public Shared Sub SetProperty(ByVal de As DirectoryEntry, ByVal propName As String, ByVal propValue As String)
If Not propValue Is Nothing Then
If de.Properties.Contains(propName) Then
de.Properties(propName)(0) = propValue
Else
de.Properties(propName).Add(propValue)
End If
End If
End Sub
Public Shared Function GetDirectoryEntry(ByVal path As String) As DirectoryEntry
Dim de As New DirectoryEntry()
de.Path = path
de.Username = "<username>"
de.Password = "<password>"
'Not setting the username or password or setting both to Nothing throws the error
de.AuthenticationType = AuthenticationTypes.Secure
Return de
End Function
Dim de As DirectoryEntry = GetDirectoryEntry("<path>")
Dim searcher As DirectorySearcher = New DirectorySearcher(de)
searcher.Filter = "(&(objectCategory=person)(objectClass=user)(cn=" & fullName & "))"
searcher.SearchScope = SearchScope.SubTree
Dim result As SearchResult = searcher.FindOne()
If Not result Is Nothing Then
Dim deResult As New DirectoryEntry(result.Path)
SetProperty(deResult, "accountExpires", toAccountExpirationDate)
deResult.CommitChanges()
deResult.Close()
End If
de.Close()

In order to not have to specify any credentials before doing the operation, either the user IIS is running under needs to have AD editing privileges (which by default it most certainly does not), or you need to set Impersonation and use Windows authentication so that it runs as the user viewing the page.
The second case has an extra difficulty due to impersonation not being able to "double hop", that is the webserver would also have to be a domain controller or you'd have to set some extra AD delegation privileges on the server that your domain admins might not want to give you.
The solution for your problem in this case is to change the user account your application is running under to one that already has the permissions you need. The danger in that is that any security hole will give the attacker those same privileges.
Alternately, you can encrypt some credentials and decrypt to use them, which is slightly better then hard coding. I guess having the users supply credentials manually and then using them the same way you're currently using hard coded ones would also work.

Related

MS-Access in Sharepoint - what data available?

For better or worse we are launching an Access db from Sharepoint. Note that this db is not PUBLISHED to SP, people just double-click the link and open the db on their desktops.
So now we need to begin imposing the equivalent of some roles-based edit restrictions. I know there is a VBA CurrentWebUser function and a CurrentWebUserGroups which provides some basic data about who's accessing an Office file from Sharepoint. However my reading and limited experimenting with this stuff leads me to suspect that, for Access at least, these will only work with published dbs, and not ones that are just being launched and run locally, like we're doing.
Is there anything I can get from SP in a case like this? Web user and user group would be useful, so would whichever site/page the link is being clicked on. Is any of this available?
Thanks.
rabbit
Well, not in any simple way.
As you've already determined, Application.CurrentWebUser just returns Null.
However, there are several ways to query the user information from SharePoint.
The recommended way (also by me) if you're going to work with SharePoint extensively, is to use the CSOM api, which requires a .Net language, so you'll have to create a COM module, authenticate it separately, and that's all a lot of work.
However, if you're only using simple GET requests, you can also use the REST API and re-use the authentication MS Access uses itself (since MS Access uses MSXML2 to submit web requests to SharePoint, we can create our own MSXML2.XMLHTTP object and it will re-use the cookies Access uses).
The following code uses the JSONInterpreter object I've shared here on GitHub. You could convert it to use XML and MSXML if you don't want that dependency, though.
To execute a request, I use the following code, that assumes the Access application is authenticated, but if it isn't, it connects to the SharePoint site using ADO.
(For this code, MySiteName is a global variable containing the URL of your SharePoint site, without a trailing slash)
Public Function SPRestGetJSON(Site As String, Request As String) As String
Dim tries As Long
Dim Success As Boolean
Do
'Try to execute request
tries = tries + 1
Dim xmlHttpReq As Object 'MSXML2.XMLHTTP60
Set xmlHttpReq = CreateObject("Msxml2.XMLHTTP.6.0") 'New MSXML2.XMLHTTP60
xmlHttpReq.Open "GET", Site & Request, False
xmlHttpReq.setRequestHeader "Content-Type", "application/json"
xmlHttpReq.setRequestHeader "Accept", "application/json;odata=nometadata"
xmlHttpReq.send
Dim root As JSONInterpreter
Set root = New JSONInterpreter
root.JSON = xmlHttpReq.responseText
If Not root.Exists("odata.error") Then
Success = True
End If
If Not Success And tries = 1 Then
'Connect to SharePoint using WSS + ADO to create auth cookies inside MSXML
Dim conn As Object 'ADODB.Connection
Set conn = CreateObject("ADODB.Connection") 'New ADODB.Connection
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;WSS;DATABASE=" & Site
On Error Resume Next
conn.Execute "SELECT 1 From SomeTable" 'Execute to non-existent table but connect to sharepoint
On Error GoTo 0
conn.Close
End If
Loop While tries < 2 And Success = False
SPRestGetJSON = xmlHttpReq.responseText
End Function
Then, we can use that in a simple function:
Public Function GetSPUsername() As String
Dim jsi As New JSONInterpreter
jsi.JSON = SPRestGetJSON(MySiteName, "/_api/Web/CurrentUser")
GetSPUsername = jsi.item("LoginName").VBAVariant
End Function
Getting groups is also available. This code returns an array of dictionary objects, you can view the available keys in the locals window:
Public Function GetSPGroups() As Variant 'Array of dictionaries
Dim jsi As New JSONInterpreter
jsi.JSON = SPRestGetJSON(SiteName, "/_api/Web/CurrentUser/Groups")
GetSPGroups = jsi.item("value").VBAVariant
End Function
Then, to get the title of the first group the current user is a member of in the immediate window, we can use:
?GetSPGroups(0)!Title

Is there a direct way to copy files from ServerA to ServerB when only one of them requires impersonation?

I am trying to copy files from Server A to Server B.
So basically, it's fast and easy, File.Copy.
File.Copy("\\ServerA\c$\CopyImageTest\Server61.txt", "\\ServerB\c$\CopyImageTest\Server61.txt")
File.Copy("\\ServerB\c$\CopyImageTest\Server182.txt", "\\ServerA\c$\CopyImageTest\Server182.txt")
Since Server A requires login to use, it's obvious that I got this error:
System.IO.IOException: Logon failure: unknown user name or bad password.
I've looked around and found that I should use Impersonation in order to access that server and copy the files. I found this answer which is in C# and converted it to VB.NET.
Here's the code that I am using:
Module Module1
<PermissionSetAttribute(SecurityAction.Demand, Name:="FullTrust")> _
Sub Main()
Try
'Some declarations omitted for clearance.
Const LOGON32_PROVIDER_DEFAULT As Integer = 0
Const LOGON32_LOGON_INTERACTIVE As Integer = 2
Const LOGON32_LOGON_NEW_CREDENTIALS As Integer = 9
Try
Dim bImpersonated As Boolean = LogonUser(sUsername, sDomain, sPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, pExistingTokenHandle)
If (Not bImpersonated) Then
Dim nErrorCode As Integer = Marshal.GetLastWin32Error()
Return
End If
Dim bRetVal As Boolean = DuplicateToken(pExistingTokenHandle, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, pDuplicateTokenHandle)
If (Not bRetVal) Then
Dim nErrorCode As Integer = Marshal.GetLastWin32Error()
CloseHandle(pExistingTokenHandle)
Return
Else
Dim newId As WindowsIdentity = New WindowsIdentity(pDuplicateTokenHandle)
Dim impersonateduser As WindowsImpersonationContext = newId.Impersonate()
'Shouldn't I be copying now?
File.Copy("\\ServerA\c$\CopyImageTest\Server61.txt", "\\ServerB\c$\CopyImageTest\Server61.txt")
File.Copy("\\ServerB\c$\CopyImageTest\Server182.txt", "\\ServerA\c$\CopyImageTest\Server182.txt")
sResult = sResult & "After impersonation: " & WindowsIdentity.GetCurrent.Name & Environment.NewLine
impersonateduser.Undo()
End If
Catch ex As Exception
'Omitted
Finally
If (Not pExistingTokenHandle = IntPtr.Zero) Then
CloseHandle(pExistingTokenHandle)
End If
If (Not pDuplicateTokenHandle = IntPtr.Zero) Then
CloseHandle(pDuplicateTokenHandle)
End If
End Try
Catch ex As Exception
'Omitted
End Try
End Sub
' DLL Imports and Enum Omitted.
End Module
Some code to output the results has been omitted, but here's the results from the console:
User was able to login. (This is used after LogonUser()) (Previously it was throwing an error (1326) Logon failure: unknown user name or bad password)
Before impersonation: User-PC\User (Result of WindowsIdentity.GetCurrent().Name; Before Impersonate())
After impersonation: User-PC\User (Result of WindowsIdentity.GetCurrent().Name; After Impersonate())
System.IO.IOException: Logon failure: unknown user name or bad password.
What's confusing is that the name before and after impersonation is the same as my local PC. However the login is working. (I am positive about that because, as stated, it was failing previously.)
Secondly, the file still doesn't get copied with same error as before:
Logon failure: unknown user name or bad password.
Is this how actually Impersonation should be working, or am I missing something obvious?
After digging a bit more, I also found this answer and I used the same exact code from the answer, but with no luck at all.
Some information about the Server A that I am trying to access and copy the files from:
I can connect through remote desktop connecting using the same username/password.
I can open the file explorer from my own PC using the server IP address as shown in the code: \\ServerA\c$\CopyImageTest.
Both my PC and the server are on the same network, but different domains.
Update:
#Esko's comment guided me to make two or three tests.
Here's the scenario:
With Impersonation I tried this code:
File.Copy("\\ServerA\c$\CopyImageTest\Server61.txt", "C:\CopyImageTest\Server61.txt")
File.Copy("C:\CopyImageTest\Server182.txt", "\\ServerA\c$\CopyImageTest\Server182.txt")
Which means that I only copied the files from ServerA to local and vice versa.
Result: Passed.
With Impersonation
File.Copy("C:\CopyImageTest\Server61.txt", "\\ServerB\c$\CopyImageTest\Server61.txt")
File.Copy("\\ServerB\c$\CopyImageTest\Server182.txt", "C:\CopyImageTest\Server182.txt")
Which means I tried to copy from ServerB to local and vice verca
Result: Failed
Without impersonation
File.Copy("C:\CopyImageTest\Server61.txt", "\\ServerB\c$\CopyImageTest\Server61.txt")
File.Copy("\\ServerB\c$\CopyImageTest\Server182.txt", "C:\CopyImageTest\Server182.txt")
Copying from and to ServerB
Result: Passed
So at the end, I think the problem is that when Impersonating the user to access ServerA it fails to access ServerB, knowing that ServerB doesn't require username password when trying to access it from my PC. Maybe because my PC and ServerB uses same domain on same network but ServerA uses another domain on same network?
Is there any workaround to access both servers with a single impersonation?
Note: I am aware of being able to copy files from ServerA to local then undo impersonation and copy to ServerB, I am just looking for a more straight way.

Login to Database in CrystalReports

I have created Reports using CrystalReports adding four Access Database Connections.
My requirement is, It should never prompt for UserID and Password After I install It on Client's Computer.
I have also used parameters in Reports.
I know about how to pass login credentails design time but not sure If It will prompt again after Installation at Client PC.
I also Tried
CrystalDecisions.Shared.ConnectionInfo
and went succeed, but failed when tried with report from multiple databases.
To set the credentials for multiple databases, use the following code
Dim Report1 As New CrystalReport1
Dim i As Integer
For i = 0 To Report1.DataSourceConnections.Count - 1
Dim AccessDBFileName As String = IO.Path.GetFileName(Report1.DataSourceConnections.Item(i).ServerName)
Dim Password As String = ""
Select Case AccessDBFileName
Case "Database1.mdb"
Password = "Password1"
Case "Database2.mdb"
Password = "Password2"
Case "Database3.mdb"
Password = "Password3"
Case "Database4.mdb"
Password = "Password4"
End Select
Report1.DataSourceConnections.Item(i).SetLogon("", Password)
Next
The GetFileName function retrieves the database file name from the path of the Access file.

Authenticating against ADAM using LDAP

I'm trying to authenticate using ADAM and LDAP. I really have no experience with this stuff, but I've been thrown in the deep end at work to figure it out.
Here's what I know. I'm using a program called JXplorer to look at the ADAM server, running on a VM on my computer. Here are the login details
This works perfectly. What I'm trying to do is replicate this process using VB.NET. I've tried a bunch of stuff and nothing seems to be working, I'm getting constant exceptions, ranging from bad password to unknown error. Here's the code I've started with -
Dim userName As String = "ADAM_TESTER"
Dim userPassword As String = "password"
Dim serverAddress As String = "LDAP://10.0.0.142:389"
Private Sub Main_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
Dim de As DirectoryEntry = New DirectoryEntry("LDAP://10.0.0.142:389/OU=Users,DC=TEST,DC=corp", userName, userPassword)
Dim deSearch As DirectorySearcher = New DirectorySearcher()
deSearch.SearchRoot = de
deSearch.Filter = "(&(objectClass=user) (cn=" + userName + "))"
Dim results As SearchResultCollection = deSearch.FindAll()
If (results.Count > 0) Then
Dim d As DirectoryEntry = New DirectoryEntry(results(0).Path, userName, userPassword)
If (d.Guid.ToString IsNot Nothing) Then
'The directory entry is valid
'DoSomething()
End If
End If
I've also tried changing the userName above to the details in User DN in JXplorer. I'm really stuck here and have been looking for answers for hours.
Any help would be appreciated.
FYI, Users is a container, not an OU. I believe you could have also used "LDAP://10.0.0.142:389/CN=Users,DC=TEST,DC=corp"
It is almost certainly a need for userName to be the full DN. ADAM needs a full DN for logins in most cases.
Thanks for the thoughts Geoff, I eventually figured it out. It turned out that I needed the connection string not including the OU=Users. The final string ended up being -
LDAP://10.0.0.142:389/DC=TEST,DC=corp
I've no idea why it didn't want the OU=Users. I spend about a day trying all the different combinations until finally this was accepted.

Using LDAP/AD to control access to a program - what is the account password?

Despite the fact that I know almost nothing about LDAP and AD apart from the basics of what it does (in this case, Microsoft) I have been asked to modify a program so that it will retrieve the user's email address. The application can be configured to use LDAP. I'm not sure exactly how it is using it - something to do with synchronising the application's passwords with the user's LDAP password. The application has a password, which might not be the same as the user's LDAP password. There is some mechanism to synchronise the passwords - but in theory that shouldn't concern me - I'm told that it works.
Of course, we didn't have an LDAP server here for me to try things out on.
So I have installed Windows 2003 server on a PC, and enabled LDAP on it (a massive learning curve, I can tell you). I have added a couple of users. and even managed to get one of them to be a member of Domain Admin.
In the application I'm working on, there is a setup facility to configure the connection to the LDAP. This has a helpful Test Settings button. If I put in the server IP address and the port number, and press Test Settings then it tells me that the test was successful. Hmm - that seems unlikely. There are other pieces of information it asks for:
Service Account DN
Service Account Password
User Account Container.
If I leave those blank, and save the settings then I am now able to start up the application and put only the username in with any or no password - which is not right, of course - it should prevent me from starting up my app. So I can only assume that I need to set up those three pieces of info.
The LDAP server I created is called, I believe, Aware.Server
So I have put DC=AWare, DC=Server into the Service account DN (I'm just guessing here, not really sure what should be in there) and cn=Users, DC=AWare.Server into the User Account Container (again, I don't really know what that is, or what is meant to be in there).
I have no idea what the Service Account Password is, so I leave that blank.
When I press Test Settings it asks me for a user name and password.
If I leave those blank then it says that the test was successful. I'm beginning to worry.
If I put in a user name that I have entered into LDAP, with a password, it says that the test was successful.
Actually, if I put anything at all into those boxes, it says that it is successful.
However, if I put something into the Service Account Password then the test is not successful - it says that the service account supplied has either an invalid user name or password.
So the main question at this point is - how do I find out what the service account password is?
And is the behaviour that I'm getting to be expected?
Thanks
Steve
Code for validating sign on includes:
Public Shared Function ValidateUser(ByVal server As String, ByVal port As Integer, ByVal userBase As String, ByVal userName As String, ByVal password As String, ByVal bindUser As String, ByVal bindPassword As String) As LdapValidatorResult
Dim retVal As New LdapValidatorResult
Dim conn As New Novell.Directory.Ldap.LdapConnection()
Try
'connect to the specificed server for user validation
conn.Connect(server, port)
retVal.Result = LdapValidatorResultType.Success
Try
'now authenticate to we can then go on to see if the username specificed exists
conn.Bind(bindUser, bindPassword)
'construct the distinguished name to uniquely id the specified user
Dim searchString As String = String.Format("CN={0},{1}", userName, userBase)
'look to see if the user attempting to login to a-ware exists
Dim sResults As LdapSearchResults = conn.Search(searchString, Novell.Directory.Ldap.LdapConnection.SCOPE_SUB, Nothing, Nothing, False) '"(&(!(objectClass=computer)))"
If sResults.hasMore Then
Try
'now validate the user with the password as the final check
Dim userDN As String = sResults.next.DN
conn.Bind(userDN, password)
retVal.Result = LdapValidatorResultType.Success
Catch ex As Novell.Directory.Ldap.LdapException
If ex.ResultCode = Novell.Directory.Ldap.LdapException.INVALID_CREDENTIALS Then
retVal.Result = LdapValidatorResultType.InvalidUserNameOrPassword
End If
retVal.ExceptInfo = ex
End Try
Else
retVal.Result = LdapValidatorResultType.NonExistentUser
End If
Catch ex As Novell.Directory.Ldap.LdapException
Select Case ex.ResultCode
Case Novell.Directory.Ldap.LdapException.INVALID_CREDENTIALS
retVal.Result = LdapValidatorResultType.InvalidBindUserNameOrPassword
Case Novell.Directory.Ldap.LdapException.CONNECT_ERROR
retVal.Result = LdapValidatorResultType.ServerDown
End Select
retVal.ExceptInfo = ex
End Try
Catch ex As Novell.Directory.Ldap.LdapException
Dim cause As System.Net.Sockets.SocketException = TryCast(ex.Cause, System.Net.Sockets.SocketException)
If cause IsNot Nothing Then
Select Case cause.ErrorCode
Case 1101
retVal.Result = LdapValidatorResultType.InvalidNameIp
Case 1106
retVal.Result = LdapValidatorResultType.RefusedConnectionOnPort
Case 10060
retVal.Result = LdapValidatorResultType.ServerDown
End Select
End If
retVal.ExceptInfo = ex
End Try
Return retVal
End Function
It looks like it wants you to create a user account in your domain for the app to use. Every object has a distinguished name (DN). So it's asking for the DN of the user you created and its password, as well as the DN of the container that you created the user in. I can't tell what your domain DNS name is, but I'm guessing it's AWare.Server. So, if you create a user with username appuser in the default Users container with the password P#ssw0rd, then you would give the following:
Service Account DN: CN=appuser,CN=Users,DC=AWare,DC=Server
Service Account Password: P#ssw0rd
User Account Container: CN=Users,DC=AWare,DC=Server