6

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.

Matthieu
  • 4,605
  • 4
  • 40
  • 60
  • You still don't have access to the TFS server from the machine you're currently running this code on. You need to check for any additional security/permission settings within TFS. I don't believe this is a programming problem. – qJake Sep 29 '11 at 20:11
  • @SpikeX : I have access to the TFS Server from the machine. I opened the required ports, and have access to the URI through a web browser with same credentials I used with an Impersonator. If the web client works, I expect my program to do the same, but it does not. I can see the info that should be retrieved by my program if it was able to connect (Collection and Project List). I can see why it is blurry between a programmation error (missing or wrong credentials initialization) and a network / server error (configuration issue). – Matthieu Sep 29 '11 at 20:18
  • Then my next stupid question is, your URI contains `http://` and a port number, correct? – qJake Sep 29 '11 at 20:21
  • It does : http://:8080/tfs . I tried with ip as well, even if the servername is set in the hosts file. the program works perfectly if I am inside the domain. If there is no programming solution to this issue, I'm considering trying RODC ( http://technet.microsoft.com/en-us/library/cc732801(WS.10).aspx ) and then I'll migrate this question to server fault. But there must be something I'm missing to have a program working like the webclient on a non-domain machine. – Matthieu Sep 29 '11 at 20:28
  • Did you try it without `/tfs`? My TFS code that connects to an intranet server here doesn't have that (the library may add it automatically). – qJake Sep 29 '11 at 20:30
  • @SpikeX : It is a 404 without /tfs : Team Foundation services are not available from server http://:8080/ Technical information (for administrator): HTTP code 404: Not Found. You have 3 basics urls on TFS : Notification / Server / Web Access. They can be changed independently to anything you want. In your case, your notification url must have been simplified, or it is redirected. – Matthieu Sep 29 '11 at 20:36
  • Have you used a network analyzer like wireshark or a debugging proxy like fiddler to determine what's being presented to the server between the TFS OM calls and the web browser invocation? – Edward Thomson Sep 29 '11 at 21:44
  • @EdwardThomson : you got it ! after a few session of wireshark, It was obvious that the user was malformed or not the one I thought being sent depending on the method I was using. The closest I got to a good user was with your answer, but I used the other constructor : new NetworkCredential("username", "password", "DOMAIN") otherwise the user ends up malformed ("\DOMAIN\User" instead of "DOMAIN\User"). I'll gladly upvote and accept your answer if you change it for the correct constructor :) (PS: I knew it was programming !! :P) – Matthieu Sep 30 '11 at 14:42
  • Ah, yep, I definitely gave you the wrong code, sorry! I forgot there was a three-arg ctor to use for domain, my fault. Will correct. Glad it's working! – Edward Thomson Sep 30 '11 at 17:04

1 Answers1

4

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.)

Edward Thomson
  • 74,857
  • 14
  • 158
  • 187
  • No still not working. There are many ways to impersonate a user for TFS, and I tested a few of them without success. This one gives the same TF30063 error. I'll update my question to present the different version of code I tested so far. – Matthieu Sep 29 '11 at 20:57
  • Thanks for the edit, this is much more clear. Out of curiosity, what happens if you call tfs.ensureAuthenticated() instead of trying to query the catalog data? I assume you still get an error? – Edward Thomson Sep 29 '11 at 21:47
  • Yes It's the same error. I've been doing some network analyzing. I'll answer your comment with the result. – Matthieu Sep 30 '11 at 12:48