I need to "impersonate" a user in a VB.NET 2008 WinForms application, so that the application can accept the Active Directory login of any user on a PC regardless of who is actually logged in to Windows. I want the application's My.User to be the AD account of the person who logged in to the application. I succeeded in this with the following code:
Private Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As String, ByVal lpszDomain As String, _
ByVal lpszPassword As String, ByVal dwLogonType As Integer, _
ByVal dwLogonProvider As Integer, ByRef phToken As IntPtr) As Boolean
Const LOGON32_LOGON_INTERACTIVE As Long = 2
Const LOGON32_LOGON_NETWORK As Long = 3
Const LOGON32_PROVIDER_DEFAULT As Long = 0
Const LOGON32_PROVIDER_WINNT35 As Long = 1
Const LOGON32_PROVIDER_WINNT40 As Long = 2
Const LOGON32_PROVIDER_WINNT50 As Long = 3
' Influenced from the example at http://aspalliance.com/39
Public Shared Function Login(ByVal uid As String, ByVal pwd As String) As Boolean
' Get the user's domain name.
Dim domainName As String = My.User.Name.Substring(0, My.User.Name.IndexOf("\"))
' This token is returned by the LogonUser API call (variable is passed ByRef).
Dim token As IntPtr
If LogonUser(uid, domainName, pwd, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token) Then
' Added this line per response to this question:
WindowsIdentity.Impersonate(token)
' If the login succeeds, then impersonate that user by changing CurrentPrincipal.
Dim wi As New Principal.WindowsIdentity(token)
Dim wp As New Principal.WindowsPrincipal(wi)
My.User.CurrentPrincipal = wp
Return True
Else
Return False
End If
End Function
However, the application uses a .DLL with the Data Access Layer which is connecting to SQL Server 2000. It appears that SQL Server, using "Integrated Security=SSPI" in the connection string, is receiving the login of the account logged in to Windows and not the account returned My.User.CurrentPrincipal.Identity, when stepping through the code, in both the WinForms app code and the .DLL's app code.
Both the WinForms app and .DLL code properly recognize My.User.CurrentPrincipal.Identity as the account logged in to the app, not Windows. It's just not propagating to SQL Server. This is evidenced by Stored procedures writing SUSER_SNAME() to a table's column in T-SQL.
Can anyone see what I'm going wrong?
EDIT: I've added the line WindowsIdentity.Impersonate(token) as stated, but now when my .DLL tries to create an SQL Server connection it throws this error:
Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'.
You need to call WindowsIdentity.Impersonate();:
If LogonUser(...) Then
WindowsIdentity.Impersonate(token)
Related
I'm busy testing SSRS to see if it's a viable alternative to our current reporting solution. I've set up SSRS on my local machine and have developed a working report using SQL Server Report Builder. Now what I'm trying to do is to call the report from within a WinForms application and display it in a ReportViewer control. The problem is that I've set up SQL Server to use SQL Server Authentication and I'm struggling to figure out how to connect to it programmatically.
The code I've pieced together so far looks like this:
Imports Microsoft.Reporting.WinForms
Public Class frmMain
Public v_report_name As String = "TestReport"
Public v_report_server As String = "http://elnah-ict-dt006:80"
Public v_report_path As String = "/reports_SSRS/"
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'create parameter array
Dim paramlist As New List(Of Microsoft.Reporting.WinForms.ReportParameter)
'create a specific parameter required by the report
Dim param1 As New Microsoft.Reporting.WinForms.ReportParameter("ClientID")
'add values to the parameter here we use a variable that holds the parameter value
param1.Values.Add("0279")
'add parameter to array
paramlist.Add(param1)
'Set the processing mode for the ReportViewer to Remote
ReportViewer1.ProcessingMode = ProcessingMode.Remote
'use the serverreport property of the report viewer to select a report from a remote SSRS server
ReportViewer1.ServerReport.ReportServerUrl = New System.Uri(v_report_server)
ReportViewer1.ServerReport.ReportPath = v_report_path & v_report_name
'select where the report should be generated with the report viewer control or on the report server using the SSRS service.
'Me.ReportViewer1.ProcessingMode = Microsoft.Reporting.WinForms.ProcessingMode.Remote
'add the parameterlist to the viewer
ReportViewer1.ServerReport.SetParameters(paramlist)
Me.ReportViewer1.RefreshReport()
End Sub
End Class
When it hits the SetParameters line towards the bottom, it gets the following error message:
Microsoft.Reporting.WinForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.MissingEndpointException
HResult=0x80131500
Message=The attempt to connect to the report server failed. Check your connection information and that the report server is a compatible version.
Source=Microsoft.ReportViewer.WinForms
I've tried to find examples of how to set the username and password but from what I can tell, most examples are focused on using Windows Authentication. I've tried the following line but it doesn't work:
ReportViewer1.ServerReport.ReportServerCredentials = New ReportServerCredentials("SA", "mypassword")
I haven't worked in VB.NET for ages so please excuse any obvious errors.
Here's some code from a Web Forms project I was part of the team for recently:
private void SetCredentials()
{
var userName = ConfigurationManager.AppSettings["SSRSUserName"];
var passwordEncrypted = ConfigurationManager.AppSettings["SSRSUserPasswordEncrypted"];
var passwordPlainText = SI.Crypto3.Crypto.Decrypt(passwordEncrypted, PASSPHRASE);
var domain = ConfigurationManager.AppSettings["SSRSUserDomain"];
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(passwordPlainText) && !string.IsNullOrEmpty(domain))
{
this.EventsHubReportViewer.ServerReport.ReportServerCredentials = new ReportServerCredentials(userName, passwordPlainText, domain);
}
}
That's C# but hopefully you can see that the important part is that last line. I think that the equivalent in your case should be:
ReportViewer1.ServerReport.ReportServerCredentials = New ReportServerCredentials(userName, password, domain)
The domain value can be an empty String if your on the same domain as the server.
EDIT:
I looked more closely and the ReportServerCredentials class that code is using is one of our own. In your case, you can use the Microsoft.ReportViewer.WinForms.ReportServerCredentials class, which I don't think has a constructor like that. Looking at the documentation for the NetworkCredentials property of that type indicates that you need to do this:
Dim credentials As New NetworkCredential(userName, password, domain)
ReportViewer1.ServerReport.ReportServerCredentials.NetworkCredentials = credentials
Sometimes when my customers turn on or restart their computer, and open my vb.net application directly, the application opens before SQL Server has started completely.
This results in many unexpected behaviors. To avoid this situation, I need to start a splash screen and check the SQL Server state within it, and only when SQL Server state indicates that it is loaded completely, can I run the whole application.
The question is: how to check the SQL Server state, whether it is finished loading or not? The whole SQL Server, not the database.
You can instantiate a timer in your splash form that checks if it can log into the database every one second (or whatever interval you like). I'd invoke the splash form as modal so the calling app can't continue until the splash form has detected the connection and closed itself.
At the very least you need the server name to check the connection for. If it is using a named instance then the server name should also include the instance name in the format "myserver\myinstance".
I've encapsulated the connection checking logic in the 3 overloaded functions IsConnected. You can use these functions in your splash form to check connection from the timer tick. (Each depends on the next). You can use whichever function overload is suitable based on the input items you have available.
For the first overload, if the app is running under a Windows security context that can connect to the db server then you don't need to provide the username and password (pass as empty string), otherwise you need to provide those credentials needed to login to the db server. Or you can provide your own connection string or connection object for the other overloads.
(code within the splash form)...
Private Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
If Me.IsConnected("(local)\SQL2008R2", "", "") Then Me.Close()
End Sub
Public Function IsConnected(ServerName As String, UserID As String, Password As String) As Boolean
Dim connStr As String = String.Format("Data Source={0}", ServerName)
If Not String.IsNullOrEmpty(UserID) Then
connStr &= String.Format(";User ID={0};Password={1}", UserID, Password)
Else
connStr &= ";Integrated Security=True"
End If
Return IsConnected(connStr)
End Function
Public Function IsConnected(Connection As String) As Boolean
Static conn As SqlConnection
If conn Is Nothing Then
conn = New SqlConnection(Connection)
conn.Open()
End If
Return IsConnected(conn)
End Function
Public Function IsConnected(ByRef Conn As SqlConnection) As Boolean
If Conn IsNot Nothing Then Return (Conn.State = ConnectionState.Open)
Return False
End Function
I'd invoke the splash form from the main app as a modal dialog, as such, so the app is blocked until the connection is detected.
(from the calling app form...)
frm_Splash.ShowDialog()
I'm getting the error "The remote server returned an error: (550) File unavailable (e.g., file not found, no access)." when I call my function sendFile2FTP
Function sendFile2FTP(fileNameLocal As String, fileNameServer As String, user As String, password As String) As String
Dim ftpRequest As Net.FtpWebRequest = Net.WebRequest.Create(fileNameServer)
ftpRequest.Credentials = New Net.NetworkCredential(user, password)
ftpRequest.Method = Net.WebRequestMethods.Ftp.UploadFile
Try
Dim ficheiro() As Byte = System.IO.File.ReadAllBytes(fileNameLocal)
Dim ftpStream As System.IO.Stream = ftpRequest.GetRequestStream()
ftpStream.Write(ficheiro, 0, ficheiro.Length)
ftpStream.Close()
ftpStream.Dispose()
Return "True"
Catch ex As Exception
Return ex.Message
End Try
End Function
And this are the parameters that i'm sending to the function (which are all valid)
fileNameLocal -> C:\Users\user\Documents\Visual Studio 2013\Projects\AgenteExportDebitosCC\AgenteExportDebitosCC\bin\Debug\file02-05-2014.xml
fileNameServer -> ftp://ftp.server.com/intranet/file02-05-2014.xml
user -> user
password ->password
What am I doing wrong?
Edit:
I'm not sure if this is a permission issue, but I am able to create files with filezilla using the same credentials...
The issue was regarding the ftp address. Instead of
ftp://ftp.server.com/intranet/file02-05-2014.xml
I had to use the username in the address
ftp://username#ftp.server.com/server.com/intranet/file02-05-2014.xml
I've got a question regarding Visual Basic's local database. So far I've managed to: 1. Create a Local Database, named it Database1 2. Create a table with values (username, password, year/section, secretquestion, secretanswer) 3. Create a dataset in form1(to get form2's entries) 4. Create a registration form in form 2( 5 labels/textboxes to get username,password,year/section,secretquestion,secretanswer)
Here's my current code for Form2:
Public Class Form2
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim sqlConnection1 As New System.Data.SqlClient.SqlConnection("Data Source=C:\Users\Bounty Hounds\AppData\Local\Temporary Projects\WindowsApplication1\Database1.sdf")
Dim cmd As New System.Data.SqlClient.SqlCommand
cmd.CommandType = System.Data.CommandType.Text
cmd.CommandText = "INSERT Username (user) Password (pass) Year/Section (yns) SecretQuestion (sq) SecretAnswer (sa)"
cmd.Connection = sqlConnection1
sqlConnection1.Open()
cmd.ExecuteNonQuery()
sqlConnection1.Close()
End Sub
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
Dim user As String
user = TextBox1.Text
End Sub
Private Sub TextBox2_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox2.TextChanged
Dim pass As String
pass = TextBox2.Text
End Sub
Private Sub TextBox3_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox3.TextChanged
Dim yns As String
yns = TextBox3.Text
End Sub
Private Sub TextBox4_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox4.TextChanged
Dim sq As String
sq = TextBox4.Text
End Sub
Private Sub TextBox5_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox5.TextChanged
Dim sa As String
sa = TextBox5.Text
End Sub
End Class
But as soon as I click the register button it gives me an error and points at the sqlConnection1.Open() line, the error is: "A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections."
In the Database Explorer I see my Database1.sdf with an X on its icon(the small yellow cylinder) and checked that its state is closed, so I tried opening it by right clicking it, then modify connection, placed the Database1.sdf path in the Database, inputted my password and pressed ok. At that point I see my Database1.sdf state go "Open" but when I ran my application the state went to "Closed" again (btw I see this state thing by right clicking my Database1.sdf in the Solution Explorer).
This text below has little to no connection with my code above and I just wanted your opinions on what is the best approach to the software I want to create so if you only want to help me on my code which I greatly appreaciate, you can stop reading the paragraphs below but if you do take time to consider reading it, It would mean alot to me. THANKS
The connection of a visual basic form and a database is the first step of what I really want to do, what my main goal is a File Storage system between two PCs which I'll increase once I figured out those 2 PCs.
To reach that goal I tried to divide each problems so I can address them accordingly and eventually finishing the entire software.
Establish a connection between 2 PCs (which I've done by setting PC2's dns server with PC1's ip address).
Connect a visual basic form (which in my case is a registration form) with a database for storing user accounts.
Make every registration create a folder for the certain users that registered. The folders created will be the storage of their files (this idea is really vague atm as I don't know how will I do this, AND VERY IMPORTANT PART IS ONLY THE REGISTERED USERS FROM THE DATABASE ONLY HAVE ACCESS TO THE FOLDERS THEY OWN(sorry i'm not shouting, just noting this as I feel this is the hardest part to do)
Implement a disk quota on the folders to limit sizes.
Create a Login system for PC2 to connect to the database of PC1 (Database should verify this and give an error if the infos are incorrect).
Create a Save/Load button for PC2 (I want PC2 to save its myDocuments Folder on PC1's Folder for Storage Files by using the My.Computer.FileSystem.CopyDirectory command. Then load will copy the PC1's Folder and load it into PC2's MyDocuments Folder.
Lastly and the biggest question is that are all these possible to do with Visual Basic? I've tried it with Windows Active Directory using roaming user profiles but I really want to develop my own software.
EDIT:
#Jimmy Smith
Thanks for replying, I've decided to create another database named CCS and my new code for form2 is:
Dim conn As New System.Data.SqlServerCe.SqlCeConnection()
Dim cmd As New System.Data.SqlClient.SqlCommand
conn.ConnectionString = _
"Persist Security Info = False; Data Source = 'C:\Users\Bounty Hounds\Documents\Visual Studio 2010\Projects\WindowsApplication2\WindowsApplication2\CCS.sdf';" & _
"Password = joshua8; File Mode = 'shared read'; "
conn.Open()
cmd.CommandType = System.Data.CommandType.Text
cmd.CommandText = "INSERT Username (user) Password (pass) Year/Section (yns) SecretQuestion (sq) SecretAnswer (sa)"
However, It generates a new error:
There is a file sharing violation. A different process might be using the file. [ C:\Users\Bounty Hounds\Documents\Visual Studio 2010\Projects\WindowsApplication2\WindowsApplication2\CCS.sdf ]
You can't connect to SDF files direct without using the Compact Edition library. Unfortunately, it's not installed by default as Microsoft seems to be phasing it out.
Use System.Data.SqlserverCe.SqlCeConnection in place of System.Data.SqlClient.SqlConnection
http://visualstudiogallery.msdn.microsoft.com/0e313dfd-be80-4afb-b5e9-6e74d369f7a1
http://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlceconnection(v=vs.100).aspx
In our asp.net intranet application we are using windows authentication to authenticate the users.
We have recently had a request to give the user a reason for why they cannot login. For example, tell the user they can't login because their password has expired vs they can't login because their account is locked out.
When an account is locked out or the password has expired, the user cannot log on to the application. IIS will deny the access and redirect the user to the Access Denied (401) page after 3 login attempts. As the username is not passed to web application when IIS authentication fails, we won’t be able to check if the account is locked out or the password has expired.
Any suggestions on how to get this information?
Are we going to have to move to Forms authentication with an AD provider?
The simple solution to this is to move to forms authentication. But being that I know you did not want to hear that and it is not allowed or a viable solution your next option is to:
Look into System.DirectoryServices
Below I'm just pasting some quick code you can play with. Notice how to determine if a user is locked out or not. This is vb.net but can be easily changed to C#.
Try
Dim dirEntry As DirectoryEntry
dirEntry = New DirectoryEntry("LDAP://yourDomainInfoHere/OU=Users,OU=YourDomain,OU=YourOU,OU=CORP,DC=YourDC,DC=com", "ExecuateAsUser", "Password")
Dim entries As DirectoryEntries = dirEntry.Children
' Set login name and full name.
Dim newUser As DirectoryEntry = entries.Add("CN=JONNY BOY", "User")
newUser.Properties("sAMAccountName").Add("jboy")
newUser.CommitChanges()
newUser.Invoke("SetPassword", "hi2343145gfdtgwdt")
Dim flags As Integer
flags = CInt(newUser.Properties("userAccountControl").Value)
'enable user below
newUser.Properties("userAccountControl").Value = flags And Not &H2
'disable user below
newUser.Properties("userAccountControl").Value = flags Or &H1
'lockout property
Dim l As Long
l = CType(newUser.Properties("lockoutTime").Value, Long)
If l <> 0 Then
'account is locked out
'so how do we unlock it?
'we unlock it by setting it to 0
newUser.Properties("lockoutTime").Value = 0
Else
'account is 0 it is NOT locked out
End If
newUser.CommitChanges()
Dim j As DirectoryEntry = entries.Find("CN=JONNY BOY", "User")
j.Properties("mail").Value = "jon#yahoo.com"
j.CommitChanges()
Catch ex As Exception
Throw ex
End Try