web-dev-qa-db-fra.com

Authentification fédérée dans Sharepoint 2013: obtention des cookies RTFA et FedAuth

Le scénario est le suivant: Je dois effectuer une authentification fédérée d’un utilisateur (qui utilise son compte universitaire) sur le site Sharepoint de son université et pour obtenir à la fois les cookies FedAuth et rtFa (que je dois passer à SharePoint REST webservices afin d’accéder aux ressources).

J'ai fait quelques tentatives mais il y a au moins un problème dans chacune d'elles:

1) Utilisation de la bibliothèque Microsoft.SharePoint.Client

ClientContext context = new ClientContext(Host);
SharePointOnlineCredentials creds = new SharePointOnlineCredentials(user, passw);
context.Credentials = creds;

Uri sharepointuri = new Uri(Host);
string authCookie = creds.GetAuthenticationCookie(sharepointuri);

Web web = context.Web;
context.Load(web, w=>w.Lists);
context.ExecuteQuery();

fedAuthString = authCookie.Replace("SPOIDCRL=", string.Empty);

De cette façon, je parviens à obtenir le cookie FedAuth mais Je ne parviens pas à obtenir le cookie rtFa .

Comment puis-je obtenir le cookie rtFa à ce stade? Puis-je intercepter la requête HTTP impliquée dans une telle opération (c'est-à-dire context.ExecuteQuery ()) - qui contient vraisemblablement le cookie rtFa dans les en-têtes? Ou puis-je obtenir le cookie rtFa en utilisant uniquement le cookie FedAuth?

2) Utilisation de MsOnlineClaimsHelper

Il s'agit d'une classe d'assistance disponible sur Internet (par exemple, ici http://blog.kloud.com.au/tag/msonlineclaimshelper/ ).

Cette classe, telle quelle, fonctionne avec une authentification normale mais échoue avec une authentification fédérée .

Alors je l'ai ajusté afin de le faire fonctionner dans ce cas. Si je comprends bien, les étapes sont les suivantes:

  1. Authentifiez-vous à l'aide du nom d'utilisateur et du mot de passe auprès du service STS ADFS de l'université (la "partie fédérée" ou l'EDITEUR) - ici la partie utilisatrice est Sharepoint O365 STS (" https://login.microsoftonline.com/extSTS.srf ")
  2. Si l'authentification réussit, je récupère une assertion SAML contenant les revendications et un jeton de sécurité.
  3. Maintenant, je m'authentifie sur le site SharePoint en transmettant le jeton de sécurité.
  4. Si le jeton est reconnu, je reçois une réponse contenant les deux cookies (FedAuth et rtFa).

Je ne suis pas un expert en la matière et je suis sorti avec le code suivant:

C'est le code qui appelle la méthode ci-dessus et tente d'obtenir FedAuth et rtFa à partir des informations d'identification en deux étapes (étape 1: obtenir le jeton SAML de la partie fédérée; étape 2: passer le jeton de la partie fédérée à Sharepoint):

     private List<string> GetCookies(){
            // 1: GET SAML XML FROM FEDERATED PARTY THE USER BELONGS TO
            string samlToken = getResponse_Federation(sts: "https://sts.FEDERATEDDOMAIN.com/adfs/services/trust/13/usernamemixed/",
                realm: "https://login.microsoftonline.com/extSTS.srf");

            // 2: PARSE THE SAML ASSERTION INTO A TOKEN 
            var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers;
            SecurityToken token = handlers.ReadToken(new XmlTextReader(new StringReader(samlToken )));

            // 3: REQUEST A NEW TOKEN BASED ON THE ISSUED TOKEN
            GenericXmlSecurityToken secToken = GetO365BinaryTokenFromToken(token);

            // 4: NOW, EASY: I PARSE THE TOKEN AND EXTRACT FEDAUTH and RTFA
            ...............
    }


    private string getResponse_Federation(string stsUrl, string relyingPartyAddress)
    {
        var binding = new Microsoft.IdentityModel.Protocols.WSTrust.Bindings.UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);
        binding.ClientCredentialType = HttpClientCredentialType.None;

        var factory = new WSTrustChannelFactory(binding,  stsUrl);

        factory.Credentials.UserName.UserName = "username";
        factory.Credentials.UserName.Password = "password";
        factory.Credentials.SupportInteractive = false;
        factory.TrustVersion = TrustVersion.WSTrust13;

        IWSTrustChannelContract channel = null;
        try
        {
            var rst = new RequestSecurityToken
            {
                RequestType = WSTrust13Constants.RequestTypes.Issue,
                AppliesTo = new EndpointAddress(relyingPartyAddress), //("urn:sharepoint:MYFEDERATEDPARTY"),
                ReplyTo = relyingPartyAddress,
                KeyType = WSTrust13Constants.KeyTypes.Bearer,
                TokenType =  "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
                RequestDisplayToken = true,
            };
            channel = (WSTrustChannel)factory.CreateChannel();

            RequestSecurityTokenResponse response = null;
            SecurityToken st = channel.Issue(rst, out response);
            var genericToken = st as GenericXmlSecurityToken;
            return genericToken.TokenXml.OuterXml;
        }
        catch (Exception e)
        {
            return null;
        }
    }

    private GenericXmlSecurityToken GetO365BinaryTokenFromToken(SecurityToken issuedToken)
    {
        Uri u = new Uri("https://login.microsoftonline.com/extSTS.srf");

        WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
        binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
        binding.Security.Message.ClientCredentialType = MessageCredentialType.IssuedToken;

        Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory channel =
        new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
            binding, new EndpointAddress("https://login.microsoftonline.com/extSTS.srf"));

        channel.TrustVersion = TrustVersion.WSTrust13;
        channel.Credentials.SupportInteractive = false;

        GenericXmlSecurityToken token = null;

        try
        {
            RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer)
            {
            };
            rst.AppliesTo = new EndpointAddress("urn:sharepoint:MYFEDERATEDPARTY");
            channel.ConfigureChannelFactory();
            var chan = (Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel)channel.CreateChannelWithIssuedToken(issuedToken);

            RequestSecurityTokenResponse rstr = null;

            token = chan.Issue(rst, out rstr) as GenericXmlSecurityToken;

            return token;
        }
        catch (Exception ex){
            Trace.TraceWarning("WebException in getO365BinaryTokenFromADFS: " + ex.ToString());
            throw;
        }
    }

J'ai réussi à récupérer un jeton SAML de l'université STS. Cependant, une fois analysé, le SecurityToken résultant n'a pas de clé de sécurité (c'est-à-dire que la collection SecurityKeys est vide).

Sans clé, j'obtiens GetO365BinaryTokenFromToken (), mais lorsque j'essaie d'envoyer le jeton au service d'authentification SharePoint, le message d'erreur suivant s'affiche: "Le jeton de signature Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken n'a pas de clé. La sécurité Le jeton est utilisé dans un contexte qui le nécessite pour effectuer des opérations cryptographiques, mais le jeton ne contient aucune clé cryptographique. Soit le type de jeton ne prend pas en charge les opérations cryptographiques, soit l'instance de jeton particulière ne contient pas de clés cryptographiques. Vérifiez votre configuration pour vous assurer que les types de jeton désactivés (par exemple, UserNameSecurityToken) ne sont pas spécifiés dans un contexte nécessitant des opérations cryptographiques (par exemple, un jeton supportant l'approbation). "

Je pense qu'il y a également des problèmes de configuration que je ne peux pas contrôler directement, des deux côtés (l'université STS ADFS et le Sharepoint STS).

J'espère que davantage de spécialistes apporteront de la clarté dans ce processus et donneront même des conseils pour que ce scénario fonctionne réellement.

Fonction de téléchargement de fichier

Avec la fonction suivante, je peux télécharger un fichier (avec une URL telle que https://myfederatedparty.sharepoint.com/sites/MYSITE/path/myfile.pdf ) en envoyant BOTH le FedAuth et le cookie RTFA. Si je ne réussis pas le cookie rtFa, j'obtiens une réponse "non autorisé".

    public static async Task<byte[]> TryRawWsCall(String url, string fedauth, string rtfa, CancellationToken ct, TimeSpan? timeout = null) {
        try {
            HttpClientHandler handler = new HttpClientHandler();
            handler.CookieContainer = new System.Net.CookieContainer();
            CookieCollection cc = new CookieCollection();
            cc.Add(new Cookie("FedAuth", fedauth));
            cc.Add(new Cookie("rtFa", rtfa));
            handler.CookieContainer.Add(new Uri(url), cc);

            HttpClient _client = new HttpClient(handler);
            if (timeout.HasValue)
                _client.Timeout = timeout.Value;
            ct.ThrowIfCancellationRequested();

            var resp = await _client.GetAsync(url);
            var result = await resp.Content.ReadAsByteArrayAsync();
            if (!resp.IsSuccessStatusCode)
                return null;
            return result;
        }
        catch (Exception) { return null; }
    }
9
metaphori

En fait, seul le cookie FedAuth est obligatoire en ce qui concerne l'authentification SharePoint Online/Office 365.

Selon Authentification à distance dans SharePoint Online à l'aide de l'authentification basée sur les revendications :

Les cookies FedAuth permettent l’autorisation fédérée et le cookie rtFA permet de déconnecter l’utilisateur de tous les sites SharePoint, même si le processus de déconnexion démarre à partir d’un site non-SharePoint.

Donc, il suffit de fournir SPOIDCRL en-tête HTTP pour effectuer l'authentification dans SharePoint Online/Office 365, par exemple:

var request = (HttpWebRequest)WebRequest.Create(endpointUri);
var credentials = new SharePointOnlineCredentials(userName,securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
request.Headers.Add(HttpRequestHeader.Cookie, authCookie);

Les exemples suivants montrent comment effectuer une authentification active dans SharePointOnline/Office 365 en fournissant un cookie FedAuth.

Exemple 1: récupération de FormDigest via SharePoint 2013 REST API (uisng MsOnlineClaimsHelper class)

public static string GetFormDigest(Uri webUri, string userName, string password)
{
   var claimsHelper = new MsOnlineClaimsHelper(webUri, userName, password);
   var endpointUri = new Uri(webUri,"/_api/contextinfo");
   var request = (HttpWebRequest)WebRequest.Create(endpointUri);
   request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
   request.Method = WebRequestMethods.Http.Post;
   request.Accept = "application/json;odata=verbose";
   request.ContentType = "application/json;odata=verbose";
   request.ContentLength = 0;

   var fedAuthCookie = claimsHelper.CookieContainer.GetCookieHeader(webUri); //FedAuth are getting here
   request.Headers.Add(HttpRequestHeader.Cookie, fedAuthCookie); //only FedAuth cookie are provided here
   //request.CookieContainer = claimsHelper.CookieContainer;
   using (var response = (HttpWebResponse) request.GetResponse())
   {
        using (var streamReader = new StreamReader(response.GetResponseStream()))
        {
                var content = streamReader.ReadToEnd();
                var t = JToken.Parse(content);
                return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
        }     
    }
}

Exemple 2: récupération de FormDigest via SharePoint 2013 REST API (à l'aide de SharePointOnlineCredentials class)

public static string GetFormDigest(Uri webUri, string userName, string password)
{
   var endpointUri = new Uri(webUri, "/_api/contextinfo");
   var request = (HttpWebRequest)WebRequest.Create(endpointUri);
   request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
   request.Method = WebRequestMethods.Http.Post;
   request.Accept = "application/json;odata=verbose";
   request.ContentType = "application/json;odata=verbose";
   request.ContentLength = 0;

   var securePassword = new SecureString();
   foreach (char c in password)
   {
       securePassword.AppendChar(c);
   }
   request.Credentials = new SharePointOnlineCredentials(userName,securePassword);

   using (var response = (HttpWebResponse)request.GetResponse())
   {
       using (var streamReader = new StreamReader(response.GetResponseStream()))
       {
           var content = streamReader.ReadToEnd();
           var t = JToken.Parse(content);
           return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
        }
   }
}

Mise à jour

La version modifiée de l'exemple pour télécharger un fichier:

public static async Task<byte[]> DownloadFile(Uri webUri,string userName,string password, string relativeFileUrl, CancellationToken ct, TimeSpan? timeout = null)
{
        try
        {

            var securePassword = new SecureString();
            foreach (var c in password)
            {
                securePassword.AppendChar(c);
            }
            var credentials = new SharePointOnlineCredentials(userName, securePassword);
            var authCookie = credentials.GetAuthenticationCookie(webUri);
            var fedAuthString = authCookie.TrimStart("SPOIDCRL=".ToCharArray());
            var cookieContainer = new CookieContainer();
            cookieContainer.Add(webUri, new Cookie("SPOIDCRL", fedAuthString));


            HttpClientHandler handler = new HttpClientHandler();
            handler.CookieContainer = cookieContainer;

            HttpClient _client = new HttpClient(handler);
            if (timeout.HasValue)
                _client.Timeout = timeout.Value;
            ct.ThrowIfCancellationRequested();

            var fileUrl = new Uri(webUri, relativeFileUrl);
            var resp = await _client.GetAsync(fileUrl);
            var result = await resp.Content.ReadAsByteArrayAsync();
            if (!resp.IsSuccessStatusCode)
                return null;
            return result;
        }
        catch (Exception) { return null; }
 }
11
Vadim Gremyachev

J'ai créé un projet github basé sur https://stackoverflow.com/users/1375553/vadim-gremyachev 's's answer https://github.com/nddipiazza/SharepointOnlineCookieFetcher avec un projet pouvant générer ces cookies.

Il a des versions pour Windows, Centos7 et Ubuntu16 et j’ai utilisé le développement mono pour le construire de sorte qu’il soit indépendant de la plate-forme.

Destiné aux utilisateurs qui ne réalisent pas de programmes avec CSOM en c # mais qui souhaitent néanmoins pouvoir obtenir facilement les cookies.

Utilisation

Étape unique: (voir l'accès au chemin "/ etc/mono/registry" est refusé )

Sudo mkdir /etc/mono
Sudo mkdir /etc/mono/registry
Sudo chmod uog+rw /etc/mono/registry

Exécuter de programme:

Linux: ./SharepointOnlineSecurityUtil -u [email protected] -w https://tenant.sharepoint.com

Windows: SharepointOnlineSecurityUtil.exe -u [email protected] -w https://tenant.sharepoint.com

Entrez un mot de passe lorsque vous y êtes invité

Le résultat de stdout aura un cookie SPOIDCRL.

0
Nicholas DiPiazza