Programmatically access to TFS 2010 from outside the domain - c#

I'm trying to access my TFS server programmatically from outside the domain where the server is installed. A basic test program would look like this :
class Program
{
static void Main(string[] args)
{
Uri tfsUri = new Uri("<serverUri>");
TfsConfigurationServer _ConfigurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
CatalogNode projectCollectionCatalog = _ConfigurationServer.CatalogNode.QueryChildren(new[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None)[0]; // actual connection tries to happen here
}
}
Another version, with credentials forced :
class Program
{
static void Main(string[] args)
{
Uri tfsUri = new Uri("<serverURI>");
TfsConfigurationServer _ConfigurationServer = new TfsConfigurationServer(tfsUri, new NetworkCredential("<DifferentKindOfUsernames>", "<Password>"));
CatalogNode projectCollectionCatalog = _ConfigurationServer.CatalogNode.QueryChildren(new[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None)[0];
}
}
Another version with a mix of both previous version :
public class ConnectByImplementingCredentialsProvider : ICredentialsProvider
{
public ICredentials GetCredentials(Uri uri, ICredentials iCredentials)
{
return new NetworkCredential("<DifferentKindOfUsernames>", "<Password>", "<DomainOrNot>");
}
public void NotifyCredentialsAuthenticated(Uri uri)
{
throw new ApplicationException("Unable to authenticate");
}
}
class Program
{
static void Main(string[] args)
{
string _myUri = #"<serverUri>";
ConnectByImplementingCredentialsProvider connect = new ConnectByImplementingCredentialsProvider();
ICredentials iCred = new NetworkCredential("<DifferentKindOfUsernames>", "<Password>", "<DomainOrNot>");
connect.GetCredentials(new Uri(_myUri), iCred);
TfsConfigurationServer configurationServer =
TfsConfigurationServerFactory.GetConfigurationServer(new Uri(_myUri), connect);
configurationServer.EnsureAuthenticated();
}
}
And a version with an active directory Impersonator :
class Program
{
static void Main(string[] args)
{
using (new Impersonator("<DifferentKindOfUsernames>", "<DomainOrNot>", "<Password>"))
{
Uri tfsUri = new Uri("<serverUri>");
TfsConfigurationServer _ConfigurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
CatalogNode projectCollectionCatalog = _ConfigurationServer.CatalogNode.QueryChildren(new[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None)[0]; // actual connection tries to happen here
}
}
}
serverURI being in the form of http://<servername>:8080/tfs or http://<serverip>:8080/tfs (both tested, with hosts file up to date) which is what is set as Notification URL on the TFS Server. This program works inside the domain.
DifferentKindOfUsernames being anything from 'DOMAIN\Username', 'LocallyDuplicatedUsername', 'LOCALMACHINE\Username' with the appropriate password, password being the same in the domain and on the machine.
This simple access won't work outside of the domain , and I have this error :
TF30063: You are not authorized to access <serverUri>
translated in a web context (using the same process in an asp.net website), it is a 401 error :
The remote server returned an error: (401) Unauthorized.
even if (things tested so far) :
I have a mapping between the local user/password who runs the program on the domain outsider machine and an active directory account who has admin access to TFS(even with impersonation rights on TFS activated) .
I add a BackConnectionNames registry key with the domain outsider machine name and ip like described here.
I disable the loopback check like described here.
I use an Active Directory Impersonator with different combination of user / domain or machine name. Active Directory Impersonator being described here.
I added the TFS Server IP to the local internet zone (tried trusted site as well) in the domain outsider server IE security options like described here.
I have tested the access to the serverURI from a browser. The uri works and I have access to the TFS Collection if I give the credentials with DomainName\User + Password. I tested this before any of the modifications I described before. I wonder what could be the difference between the programmatic access and the browser access besides all the things I have tested so far.

You're not passing credentials to build the connection. This means that you're using your currently logged in credentials from the host outside of the domain. I'm not an expert on Windows Authentication, but I think that this can, in certain circumstances, work transparently (if the username and password are identical) but it appears to depend on the NTLM version being used, the client and server operating systems, trust relationships and security zones, the IIS configuration and perhaps the phase of the moon.
In other words, you probably want to pass the credentials to the connection:
TfsConfigurationServer _ConfigurationServer = new TfsConfigurationServer(uri, new NetworkCredential("username", "password", "DOMAIN"));
Note that it's strongly recommended to enable SSL/TLS if you're connecting to your server over an untrusted (public) network.
(I corrected this to use the three-arg constructor for NetworkCredential -- my mistake. As you note, if you put DOMAIN\username in the username argument to NetworkCredential, it will treat it as \DOMAIN\username instead of DOMAIN\username. This, I suppose, it why nobody lets me write C# code.)

Related

Why is my Azure public IP not showing up in Dns.GetHostAddresses?

I'm trying to set up a custom server on an Azure VM. I've assigned it a public IP address, which I'm able to reach and get into the server via Remote Desktop, so that part's working just fine.
But when I try to bind to the public IP address using the websocket-sharp library, it fails, saying "the host part isn't a local host name."
I've tracked this down to this file, where the following code block executes, and ends up returning false:
var host = System.Net.Dns.GetHostName ();
var addrs = System.Net.Dns.GetHostAddresses (host);
foreach (var addr in addrs) {
if (address.Equals (addr))
return true;
}
return false;
With a bit of debugging, I've determined that Dns.GetHostAddresses is showing internal IPs only, but not the external IP address. I've configured the IP address in Azure and attached it to the server, and I've turned on IP forwarding in the networking configuration and rebooted the VM, but the server still doesn't recognize its own external IP.
What am I missing?
What am I missing?
You could test Dns.GetHostAddresses with the local machine hostname, it also just could get the internal ip, it is not related to Azure VM.
If we want to get the public Ip of Azure VM with host name, we could use the Azure SDK to do that.
I also do a sample demo to get the public IP. Before that we need to registry Azure AD and assign corrosponding role to registried App. About how to registry Azure AD and create creditial file, you could refer to another SO thread and this link.
var subscriptiondId = "subscription Id";
var credentials = SdkContext.AzureCredentialsFactory.FromFile(#"path of creditial file");
var resouceGroup = "resouce group";
var hostName = "host name";
NetworkManagementClient networkManagement = new NetworkManagementClient(credentials) { SubscriptionId = subscriptiondId };
ComputeManagementClient computeManagement =
new ComputeManagementClient(credentials) {SubscriptionId = subscriptiondId};
var nic = computeManagement.VirtualMachines.GetAsync(resouceGroup, hostName).Result.NetworkProfile.NetworkInterfaces
.FirstOrDefault();
var networkIntefaceName = nic?.Id.Split('/').Last();
var ipConfiguration = networkManagement.NetworkInterfaces.GetAsync(resouceGroup, networkIntefaceName).Result.IpConfigurations.FirstOrDefault();
var publicIpAddressId = ipConfiguration?.PublicIPAddress.Id;
var ip = networkManagement.PublicIPAddresses.GetAsync(resouceGroup, publicIpAddressId?.Split('/').Last()

PrincipalContext with smartcard inserted

We have been using an application for a while that uses System.DirectoryServices.AccountManagement to communicate with the Active directory (domain context).
ContextOptions options = ContextOptions.Negotiate |
ContextOptions.SecureSocketLayer;
Using(PrincipalContext adContext = new PrincipalContext(ContextType.Domain, "AD.DOMAIN", "DC=AD,DC=intranet", options))
{
//Do stuff
}
This works fine until we insert a smartcard. As soon as we insert a smartcard with a user certificate, it will prompt for a smartcard pin as soon as it hits the PrincipalContext constructor. When cancelling out, the application will crash. When entering the correct pin, it will just keep on prompting over and over.
It seems to be linked to the TLS session which is set up in the background. The issue does not exist when we do not enable encryption. But encryption is mandatory.
Has anyone run into this issue before? Resources seem to be limited. Closest I could find was:
https://connect.microsoft.com/VisualStudio/feedback/details/3100569/initializing-contextoptions-does-not-work-in-system-directoryservices-accountmanagement-principalcontext-constructor
Thanks in advance
The PrincipalContext utilizes the internal class CredentialValidator to authenticate during the LDAP bind.
private bool BindLdap(NetworkCredential creds, ContextOptions contextOptions)
{
LdapConnection current = (LdapConnection) null;
...
current = new LdapConnection(this.directoryIdent);
...
try
{
current.SessionOptions.FastConcurrentBind();
The method FastConcurrentBind finds the certificate and as SSL is enabled in the connection options it asks for the PIN.
if fast bind is not supported Bind is called and does the same:
private void lockedLdapBind(
LdapConnection current,
NetworkCredential creds,
ContextOptions contextOptions)
{
current.AuthType =
(ContextOptions.SimpleBind & contextOptions) > (ContextOptions) 0
? AuthType.Basic
: AuthType.Negotiate;
current.SessionOptions.Signing =
(ContextOptions.Signing & contextOptions) > (ContextOptions) 0;
current.SessionOptions.Sealing =
(ContextOptions.Sealing & contextOptions) > (ContextOptions) 0;
if (creds.UserName == null && creds.Password == null)
current.Bind();
else
current.Bind(creds);
}
To prevent this the session options must be modified like this
current.SessionOptions.QueryClientCertificate =
new QueryClientCertificateCallback((a,b) => null);
The issue is that this cannot be done for the internal class from the outside.
This can only be done when manually constructing the LdapConnection object.

How to get user information when they access my website

I have a simple website in ASP.NET where I have loaded a DLL. I have published the site via IIS and I only want to show on the user side his Machine Name, logged in user and IP. I have tried the following:
My DLL:
namespace ClassLibrary1
{
public class Class1
{
public string getInfo()
{
IPAddress[] ips;
ips = Dns.GetHostAddresses(Dns.GetHostName());
string returns = null;
returns = Environment.MachineName + Convert.ToChar(9) + Environment.UserName;
foreach (IPAddress ip in ips)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
returns += Convert.ToChar(9) + ip.ToString();
}
return returns;
}
}
}
And in the website:
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
ClassLibrary1.Class1 cl = new ClassLibrary1.Class1();
Label2.Text = cl.getInfo();
}
}
The output is not what I expected. In my machine, when I access the site i get
MyMachineName Classic .NET AppPool MyIp
And when anyone else opens it, they also get those informations, not their machinename, logged in user and IP.
So my question is how to retrieve their info?
Thanks in advance.
You are pulling the stats from the machine that is serving the website, not the visitor's machine. You should probably take a look at the HttpRequest.ServerVariables NameValueCollection instead.
Some of those "variables", particularly the ones you are interested in, are derived from the headers in each web request from the client. Keep in mind that you aren't actually talking to the client's machine, these are sent to you from the client. Consequently, there's no guarantee that they will be accurate (proxy, etc.), if they're even there at all.
That said, the ones you are probably interested in are:
var ip = HttpRequest.ServerVariables["REMOTE_ADDR"];
var user = HttpRequest.ServerVariables["REMOTE_USER"]; // Windows auth
var user = HttpRequest.ServerVariables["LOGON_USER"]; // Non-Windows auth
var machine = HttpRequest.ServerVariables["REMOTE_HOST"];
Here's the list of variables to pick from.

Active Directory not working for offsite

Technology used: asp.net(C#), MVC, LINQ, Entity
I use the Active Directory for our company website. This system works for all of our employees on site and when we take our laptop offsite and use VPN.
However we recently hired some developers(offsite) who we have given VPN access to. But they are unable to run our code and I am unsure why. We have active directory users set up for them and they can connect to us through VPN.
They are running into two errors.
A username/password error.
The Server is not Operational error.
Does the VPN username/password have to match the active directory account?
If they log into their development machine does that username/password have to match the active directory account?
Is there some restriction on Active Directory and offsite I am unaware of as to why this would work for us but not our off site developers?
Below is the section of code that is giving the error: It errors on the line SearchResult rs = ds.FindOne();
public AD_CurrentUserProfile GetUserProfile(string userName)
{
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"))
using (DirectorySearcher ds = new DirectorySearcher(de))
{
ds.SearchScope = SearchScope.Subtree;
ds.Filter = ("(&(objectCategory=person)(objectClass=User)(samaccountname=" + userName + "))");
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("manager");
ds.PropertiesToLoad.Add("directreports");
SearchResult rs = ds.FindOne();
AD_CurrentUserProfile userProfile = new AD_CurrentUserProfile();
userProfile.currentUser = GetProfileFromDN(rs.Properties["distinguishedName"][0].ToString());
userProfile.managerProfile = GetProfileFromDN(rs.Properties["manager"][0].ToString(), true);
int departmentID = db.IPACS_Department.First(v => (v.name == userProfile.currentUser.department)).departmentID;
userProfile.ipacs_department = db.IPACS_Department.Find(departmentID);
if (userProfile.currentUser.userName == userProfile.ipacs_department.owner)
{
userProfile.currentUser.isManager = true;
}
// Override for Provana and upper management
if (userProfile.currentUser.department == "Provana" || userProfile.currentUser.department == "JM")
{
userProfile.currentUser.isManager = true;
}
if (rs.Properties["DirectReports"].Count > 0)
{
if (!userProfile.currentUser.isManager)
{
userProfile.currentUser.isSupervisor = true;
}
userProfile.directReports = new HashSet<AD_User>();
foreach (string value in rs.Properties["DirectReports"])
{
userProfile.directReports.Add(GetProfileFromDN(value));
}
}
return userProfile;
}
}
I never pass 'password' anywhere in any of my code, so is this something Active Directory does by default?
A connection to AD will always require windows credentials. Your code, as posted, does not supply any credentials to AD. (You pass in a user name that you are looking up, but that is not the same as supplying credentials for the connection). This will work for users whose machines are attached to the domain...because your network credentials are passed in implicitly. For the external devs, when they VPN in, they supply credentials to the VPN protocol, which allows their machines to access your network, but that doesn't mean their machines are 'joined' to the domain...so AD will still require explicit credentials from them, including a personal password or a service account password that has permissions to access AD.
This line:
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"))
essentially needs to allow a user name and password:
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"), userName, pwd)
{
de.AuthenticationType = AuthenticationTypes.None | AuthenticationTypes.ReadonlyServer;
}
...you'll have to experiment with the AuthenticationTypes flags depending on how your network admins have it set up.

Uploading files to file server using webclient class

Currently I have an application that receives an uploaded file from my web application. I now need to transfer that file to a file server which happens to be located on the same network (however this might not always be the case).
I was attempting to use the webclient class in C# .NET.
string filePath = "C:\\test\\564.flv";
try
{
WebClient client = new WebClient();
NetworkCredential nc = new NetworkCredential(uName, password);
Uri addy = new Uri("\\\\192.168.1.28\\Files\\test.flv");
client.Credentials = nc;
byte[] arrReturn = client.UploadFile(addy, filePath);
Console.WriteLine(arrReturn.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
The machine located at 192.168.1.28 is a file server and has a share c:\Files.
As of right now I am receiving an error of Login failed bad user name or password, but I can open explorer and type in that path login successfully. I can also login using remote desktop, so I know the user account works.
Any ideas on this error?
Is it possible to transfer a file directly like that? With the webclient class or maybe some other class?
Just use
File.Copy(filepath, "\\\\192.168.1.28\\Files");
A windows fileshare exposed via a UNC path is treated as part of the file system, and has nothing to do with the web.
The credentials used will be that of the ASP.NET worker process, or any impersonation you've enabled. If you can tweak those to get it right, this can be done.
You may run into problems because you are using the IP address instead of the server name (windows trust settings prevent leaving the domain - by using IP you are hiding any domain details). If at all possible, use the server name!
If this is not on the same windows domain, and you are trying to use a different domain account, you will need to specify the username as "[domain_or_machine]\[username]"
If you need to specify explicit credentials, you'll need to look into coding an impersonation solution.
namespace FileUpload
{
public partial class Form1 : Form
{
string fileName = "";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string path = "";
OpenFileDialog fDialog = new OpenFileDialog();
fDialog.Title = "Attach customer proposal document";
fDialog.Filter = "Doc Files|*.doc|Docx File|*.docx|PDF doc|*.pdf";
fDialog.InitialDirectory = #"C:\";
if (fDialog.ShowDialog() == DialogResult.OK)
{
fileName = System.IO.Path.GetFileName(fDialog.FileName);
path = Path.GetDirectoryName(fDialog.FileName);
textBox1.Text = path + "\\" + fileName;
}
}
private void button2_Click(object sender, EventArgs e)
{
try
{
WebClient client = new WebClient();
NetworkCredential nc = new NetworkCredential("erandika1986", "123");
Uri addy = new Uri(#"\\192.168.2.4\UploadDocs\"+fileName);
client.Credentials = nc;
byte[] arrReturn = client.UploadFile(addy, textBox1.Text);
MessageBox.Show(arrReturn.ToString());
}
catch (Exception ex1)
{
MessageBox.Show(ex1.Message);
}
}
}
}
when you manually open the IP address (via the RUN command or mapping a network drive), your PC will send your credentials over the pipe and the file server will receive authorization from the DC.
When ASP.Net tries, then it is going to try to use the IIS worker user (unless impersonation is turned on which will list a few other issues). Traditionally, the IIS worker user does not have authorization to work across servers (or even in other folders on the web server).

Categories