调用公共API接口导出联系人之Hotmail

总结下之前项目中用到的,如何调用公共api来获取用户邮箱的联系人列表。

这里主要说下代码的部分,因为之前还需要一些api账号的配置,不同的api区别还比较大,这次先说下微软的hotmail,msn

首先我们需要在微软的developer中心注册开发账号,同时创建一个应用程序,代表我们的程序通过它来调用api的接口,申请完之后,需要配置些域名,同时会给我们一个key,类似于密钥或者appkey的东西,一般我都配置在webapp的webconfig中。

整个需求流程大致是,首先在web页面上引导用户登录,通过msn进行oauth认证之后,返回到我们的web页面,然后通过api接口导出用户的联系人。

服务端生成登录账户

   1 /*
   2  * FILE:        WindowsLiveLogin.cs
   3  *                                                                      
   4  * DESCRIPTION: Sample implementation of Web Authentication and Delegated 
   5  *              Authentication protocol in C#. Also includes trusted 
   6  *              sign-in and application verification sample 
   7  *              implementations.
   8  *
   9  * VERSION:     1.1
  10  *
  11  * Copyright (c) 2008 Microsoft Corporation.  All Rights Reserved.
  12  */
  13 
  14 using System;
  15 using System.Collections.Generic;
  16 using System.Text;
  17 using System.Text.RegularExpressions;
  18 using System.Collections.Specialized;
  19 using System.Collections;
  20 using System.Web;
  21 using System.Web.Configuration;
  22 using System.Security.Cryptography;
  23 using System.IO;
  24 using System.Net;
  25 using System.Reflection;
  26 using System.Xml;
  27 
  28 namespace WindowsLive
  29 {
  30     /// <summary>
  31     /// Sample implementation of Web Authentication and Delegated Authentication 
  32     /// protocol. Also includes trusted sign-in and application 
  33     /// verification sample implementations.
  34     /// </summary>
  35     public class WindowsLiveLogin
  36     {
  37         /// <summary>
  38         /// Stub implementation for logging debug output. You can run
  39         /// a tool such as 'dbmon' to see the output.
  40         /// </summary>
  41         static void debug(string msg)
  42         {
  43             System.Diagnostics.Debug.WriteLine(msg);
  44             System.Diagnostics.Debug.Flush();
  45         }
  46 
  47         /// <summary>
  48         /// Initialize the WindowsLiveLogin module with the
  49         /// application ID and secret key.
  50         ///
  51         /// We recommend that you employ strong measures to protect
  52         /// the secret key. The secret key should never be
  53         /// exposed to the Web or other users.
  54         /// </summary>
  55         public WindowsLiveLogin(string appId, string secret) :
  56             this(appId, secret, null) { }
  57 
  58         /// <summary>
  59         /// Initialize the WindowsLiveLogin module with the
  60         /// application ID, secret key, and security algorithm.
  61         ///
  62         /// We recommend that you employ strong measures to protect
  63         /// the secret key. The secret key should never be
  64         /// exposed to the Web or other users.
  65         /// </summary>
  66         public WindowsLiveLogin(string appId, string secret, string securityAlgorithm) :
  67             this(appId, secret, securityAlgorithm, false) { }
  68 
  69         /// <summary>
  70         /// Initialize the WindowsLiveLogin module with the
  71         /// forceDelAuthNonProvisioned flag, policy URL, and return URL.
  72         /// 
  73         /// The 'force_delauth_nonprovisioned' flag indicates whether
  74         /// your application is registered for Delegated Authentication 
  75         /// (that is, whether it uses an application ID and secret key). We 
  76         /// recommend that your Delegated Authentication application always 
  77         /// be registered for enhanced security and functionality. 
  78         /// </summary>
  79         public WindowsLiveLogin(bool forceDelAuthNonProvisioned, string policyUrl, string returnUrl)
  80         {
  81             ForceDelAuthNonProvisioned = forceDelAuthNonProvisioned;
  82             PolicyUrl = policyUrl;
  83             ReturnUrl = returnUrl;
  84         }
  85 
  86         /// <summary>
  87         /// Initialize the WindowsLiveLogin module with the
  88         /// application ID, secret key, security algorithm and 
  89         /// forceDelAuthNonProvisioned flag.
  90         ///
  91         /// We recommend that you employ strong measures to protect
  92         /// the secret key. The secret key should never be
  93         /// exposed to the Web or other users.
  94         /// 
  95         /// The 'force_delauth_nonprovisioned' flag indicates whether
  96         /// your application is registered for Delegated Authentication 
  97         /// (that is, whether it uses an application ID and secret key). We 
  98         /// recommend that your Delegated Authentication application always 
  99         /// be registered for enhanced security and functionality. 
 100         /// </summary>
 101         public WindowsLiveLogin(string appId, string secret, string securityAlgorithm, bool forceDelAuthNonProvisioned) :
 102             this(appId, secret, securityAlgorithm, forceDelAuthNonProvisioned, null) { }
 103 
 104         /// <summary>
 105         /// Initialize the WindowsLiveLogin module with the
 106         /// application ID, secret key, security algorithm,    
 107         /// forceDelAuthNonProvisioned and policy URL use.
 108         ///
 109         /// We recommend that you employ strong measures to protect
 110         /// the secret key. The secret key should never be
 111         /// exposed to the Web or other users.
 112         /// 
 113         /// The 'force_delauth_nonprovisioned' flag indicates whether
 114         /// your application is registered for Delegated Authentication 
 115         /// (that is, whether it uses an application ID and secret key). We 
 116         /// recommend that your Delegated Authentication application always 
 117         /// be registered for enhanced security and functionality. 
 118         /// </summary>
 119         public WindowsLiveLogin(string appId, string secret, string securityAlgorithm, bool forceDelAuthNonProvisioned, string policyUrl) :
 120             this(appId, secret, securityAlgorithm, forceDelAuthNonProvisioned, policyUrl, null) { }
 121 
 122         /// <summary>
 123         /// Initialize the WindowsLiveLogin module with the
 124         /// application ID, secret key, security algorithm,    
 125         /// forceDelAuthNonProvisioned, policy URL and return URL.
 126         ///
 127         /// We recommend that you employ strong measures to protect
 128         /// the secret key. The secret key should never be
 129         /// exposed to the Web or other users.
 130         /// 
 131         /// The 'force_delauth_nonprovisioned' flag indicates whether
 132         /// your application is registered for Delegated Authentication 
 133         /// (that is, whether it uses an application ID and secret key). We 
 134         /// recommend that your Delegated Authentication application always 
 135         /// be registered for enhanced security and functionality.
 136         public WindowsLiveLogin(string appId, string secret, string securityAlgorithm, bool forceDelAuthNonProvisioned, string policyUrl, string returnUrl)
 137         {
 138             ForceDelAuthNonProvisioned = forceDelAuthNonProvisioned;
 139             AppId = appId;
 140             Secret = secret;
 141             SecurityAlgorithm = securityAlgorithm;
 142             PolicyUrl = policyUrl;
 143             ReturnUrl = returnUrl;
 144         }
 145 
 146         /// <summary>
 147         /// Initialize the WindowsLiveLogin module from the
 148         /// web.config file if loadAppSettings is true. Otherwise,
 149         /// you will have to manually set the AppId, Secret and
 150         /// SecurityAlgorithm properties.
 151         /// 
 152         /// In a Delegated Authentication scenario, you may also specify
 153         /// the return and privacy policy URLs to use, as shown in the 
 154         /// Delegated Authentication samples.     
 155         /// </summary>
 156         public WindowsLiveLogin(bool loadAppSettings)
 157         {
 158             if (!loadAppSettings) { return; }
 159 
 160             NameValueCollection appSettings = WebConfigurationManager.AppSettings;
 161             if (appSettings == null)
 162             {
 163                 throw new IOException("Error: WindowsLiveLogin: Failed to load the Web application settings.");
 164             }
 165 
 166             string forceDelAuthNonProvisioned = appSettings["wll_force_delauth_nonprovisioned"];
 167 
 168             if (!string.IsNullOrEmpty(forceDelAuthNonProvisioned) &&
 169                 (forceDelAuthNonProvisioned.ToLower() == "true"))
 170             {
 171                 ForceDelAuthNonProvisioned = true;
 172             }
 173             else
 174             {
 175                 ForceDelAuthNonProvisioned = false;
 176             }
 177 
 178             AppId = appSettings["wll_appid"];
 179             Secret = appSettings["wll_secret"];
 180             OldSecret = appSettings["wll_oldsecret"];
 181             OldSecretExpiry = appSettings["wll_oldsecretexpiry"];
 182             SecurityAlgorithm = appSettings["wll_securityalgorithm"];
 183             PolicyUrl = appSettings["wll_policyurl"];
 184             ReturnUrl = appSettings["wll_returnurl"];
 185             BaseUrl = appSettings["wll_baseurl"];
 186             SecureUrl = appSettings["wll_secureurl"];
 187             ConsentUrl = appSettings["wll_consenturl"];
 188         }
 189 
 190         /// <summary><![CDATA[
 191         /// Initialize the WindowsLiveLogin module from a settings file. 
 192         /// 
 193         /// 'settingsFile' specifies the location of the XML settings
 194         /// file containing the application ID, secret key, an optional
 195         /// security algorithm and a privacy policy URL (required for
 196         /// Delegated Auth).  The file is of the following format:
 197         /// 
 198         /// <windowslivelogin>
 199         ///   <appid>APPID</appid>
 200         ///   <secret>SECRET</secret>
 201         ///   <securityalgorithm>wsignin1.0</securityalgorithm>
 202         ///   <policyurl>http://[your domain]/[your privacy policy]</policyurl>
 203         ///   <returnurl>http://[your domain]/[your return url]</policyurl>
 204         /// </windowslivelogin>
 205         /// 
 206         /// In a Delegated Authentication scenario, you may also specify
 207         /// 'returnurl' and 'policyurl' in the settings file. 
 208         ///  
 209         /// We recommend that you store the Windows Live Login settings file
 210         /// in an area on your server that cannot be accessed through
 211         /// the Internet. This file contains important confidential
 212         /// information.      
 213         /// ]]></summary>
 214         public WindowsLiveLogin(string settingsFile)
 215         {
 216             NameValueCollection settings = parseSettings(settingsFile);
 217 
 218             string forceDelAuthNonProvisioned = settings["force_delauth_nonprovisioned"];
 219 
 220             if (!string.IsNullOrEmpty(forceDelAuthNonProvisioned) &&
 221                 (forceDelAuthNonProvisioned.ToLower() == "true"))
 222             {
 223                 ForceDelAuthNonProvisioned = true;
 224             }
 225             else
 226             {
 227                 ForceDelAuthNonProvisioned = false;
 228             }
 229 
 230             AppId = settings["appid"];
 231             Secret = settings["secret"];
 232             OldSecret = settings["oldsecret"];
 233             OldSecretExpiry = settings["oldsecretexpiry"];
 234             SecurityAlgorithm = settings["securityalgorithm"];
 235             PolicyUrl = settings["policyurl"];
 236             ReturnUrl = settings["returnurl"];
 237             BaseUrl = settings["baseurl"];
 238             SecureUrl = settings["secureurl"];
 239             ConsentUrl = settings["consenturl"];
 240         }
 241 
 242         string appId;
 243 
 244         /// <summary>
 245         /// Gets or sets the application ID.
 246         /// </summary>
 247         public string AppId
 248         {
 249             set
 250             {
 251                 if (string.IsNullOrEmpty(value))
 252                 {
 253                     if (ForceDelAuthNonProvisioned)
 254                     {
 255                         return;
 256                     }
 257 
 258                     throw new ArgumentNullException("value");
 259                 }
 260 
 261                 Regex re = new Regex(@"^\w+$");
 262                 if (!re.IsMatch(value))
 263                 {
 264                     throw new ArgumentException("Error: AppId: Application ID must be alphanumeric: " + value);
 265                 }
 266 
 267                 appId = value;
 268             }
 269 
 270             get
 271             {
 272                 if (string.IsNullOrEmpty(appId))
 273                 {
 274                     throw new InvalidOperationException("Error: AppId: Application ID was not set. Aborting.");
 275                 }
 276 
 277                 return appId;
 278             }
 279         }
 280 
 281         byte[] cryptKey;
 282         byte[] signKey;
 283 
 284         /// <summary>
 285         /// Sets your secret key. Use this method if you did not specify 
 286         /// a secret key at initialization.
 287         /// </summary>
 288         public string Secret
 289         {
 290             set
 291             {
 292                 if (string.IsNullOrEmpty(value))
 293                 {
 294                     if (ForceDelAuthNonProvisioned)
 295                     {
 296                         return;
 297                     }
 298 
 299                     throw new ArgumentNullException("value");
 300                 }
 301 
 302                 if (value.Length < 16)
 303                 {
 304                     throw new ArgumentException("Error: Secret: Secret key is expected to be longer than 16 characters: " + value.Length);
 305                 }
 306 
 307                 cryptKey = derive(value, "ENCRYPTION");
 308                 signKey = derive(value, "SIGNATURE");
 309             }
 310 
 311             get { return null; }
 312         }
 313 
 314         byte[] oldCryptKey;
 315         byte[] oldSignKey;
 316 
 317         /// <summary>
 318         /// Sets your old secret key.
 319         /// 
 320         /// Use this property to set your old secret key if you are in the
 321         /// process of transitioning to a new secret key. You may need this 
 322         /// property because the Windows Live ID servers can take up to 
 323         /// 24 hours to propagate a new secret key after you have updated 
 324         /// your application settings.
 325         /// 
 326         /// If an old secret key is specified here and has not expired
 327         /// (as determined by the OldSecretExpiry setting), it will be used
 328         /// as a fallback if token decryption fails with the new secret 
 329         /// key.
 330         /// </summary>
 331         public string OldSecret
 332         {
 333             set
 334             {
 335                 if (string.IsNullOrEmpty(value))
 336                 {
 337                     return;
 338                 }
 339 
 340                 if (value.Length < 16)
 341                 {
 342                     throw new ArgumentException("Error: OldSecret: Secret key is expected to be longer than 16 characters: " + value.Length);
 343                 }
 344 
 345                 oldCryptKey = derive(value, "ENCRYPTION");
 346                 oldSignKey = derive(value, "SIGNATURE");
 347             }
 348 
 349             get { return null; }
 350         }
 351 
 352         string oldSecretExpiryString;
 353         DateTime oldSecretExpiry;
 354 
 355         /// <summary>
 356         /// Sets or gets the expiry time for your old secret key.
 357         /// 
 358         /// After this time has passed, the old secret key will no longer be
 359         /// used even if token decryption fails with the new secret key.
 360         ///
 361         /// The old secret expiry time is represented as the number of seconds
 362         /// elapsed since January 1, 1970. 
 363         /// </summary>
 364         public string OldSecretExpiry
 365         {
 366             set
 367             {
 368                 if (string.IsNullOrEmpty(value))
 369                 {
 370                     return;
 371                 }
 372 
 373                 oldSecretExpiryString = value;
 374                 int timestampInt;
 375 
 376                 try
 377                 {
 378                     timestampInt = Convert.ToInt32(value);
 379                 }
 380                 catch (Exception)
 381                 {
 382                     throw new ArgumentException("Error: OldSecretExpiry: Invalid timestamp: "
 383                                                 + value);
 384                 }
 385 
 386                 DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
 387                 oldSecretExpiry = refTime.AddSeconds(timestampInt);
 388             }
 389 
 390             get { return oldSecretExpiryString; }
 391         }
 392 
 393         string securityAlgorithm;
 394 
 395         /// <summary>
 396         /// Sets or gets the version of the security algorithm being used.
 397         /// </summary>
 398         public string SecurityAlgorithm
 399         {
 400             set { securityAlgorithm = value; }
 401 
 402             get
 403             {
 404                 if (string.IsNullOrEmpty(securityAlgorithm))
 405                 {
 406                     return "wsignin1.0";
 407                 }
 408 
 409                 return securityAlgorithm;
 410             }
 411         }
 412 
 413         bool forceDelAuthNonProvisioned = false;
 414 
 415         /// <summary>
 416         /// Sets or gets a flag that indicates whether Delegated Authentication
 417         /// is non-provisioned (i.e. does not use an application ID or secret
 418         /// key).
 419         /// </summary>
 420         public bool ForceDelAuthNonProvisioned
 421         {
 422             set { forceDelAuthNonProvisioned = value; }
 423 
 424             get { return forceDelAuthNonProvisioned; }
 425         }
 426 
 427         string policyUrl;
 428 
 429         /// <summary>
 430         /// Sets or gets the privacy policy URL.
 431         /// 
 432         /// Set the property for Delegated Authentication, if you did 
 433         /// not provide one at initialization time.
 434         /// </summary>
 435         public string PolicyUrl
 436         {
 437             set
 438             {
 439                 if (string.IsNullOrEmpty(value) && ForceDelAuthNonProvisioned)
 440                 {
 441                     throw new ArgumentNullException("value");
 442                 }
 443 
 444                 policyUrl = value;
 445             }
 446 
 447             get
 448             {
 449                 if (string.IsNullOrEmpty(policyUrl))
 450                 {
 451                     debug("Warning: In the initial release of Delegated Auth, a Policy URL must be configured in the SDK for both provisioned and non-provisioned scenarios.");
 452 
 453                     if (ForceDelAuthNonProvisioned)
 454                     {
 455                         throw new InvalidOperationException("Error: PolicyUrl: Policy URL must be set in a Delegated Auth non-provisioned scenario. Aborting.");
 456                     }
 457                 }
 458 
 459                 return policyUrl;
 460             }
 461         }
 462 
 463         string returnUrl;
 464 
 465         /// <summary>
 466         /// Sets or gets the return URL--the URL on your site to which the consent 
 467         /// service redirects users (along with the action, consent token, 
 468         /// and application context) after they have successfully provided 
 469         /// consent information for Delegated Authentication. 
 470         /// 
 471         /// This value will override the return URL specified during 
 472         /// registration.
 473         /// </summary>
 474         public string ReturnUrl
 475         {
 476             set
 477             {
 478                 if (string.IsNullOrEmpty(value) && ForceDelAuthNonProvisioned)
 479                 {
 480                     throw new ArgumentNullException("value");
 481                 }
 482 
 483                 returnUrl = value;
 484             }
 485 
 486             get
 487             {
 488                 if (string.IsNullOrEmpty(returnUrl) && ForceDelAuthNonProvisioned)
 489                 {
 490                     throw new InvalidOperationException("Error: ReturnUrl: Return URL must be specified in a delegated auth non-provisioned scenario. Aborting.");
 491                 }
 492 
 493                 return returnUrl;
 494             }
 495         }
 496 
 497         string baseUrl;
 498 
 499         /// <summary>
 500         /// Sets or gets the URL to use for the Windows Live Login server. 
 501         /// You should not have to use or change this. Furthermore, we
 502         /// recommend that you use the Sign In control instead of
 503         /// the URL methods provided here.
 504         /// </summary>
 505         public string BaseUrl
 506         {
 507             set { baseUrl = value; }
 508 
 509             get
 510             {
 511                 if (string.IsNullOrEmpty(baseUrl))
 512                 {
 513                     return "http://login.live.com/";
 514                 }
 515 
 516                 return baseUrl;
 517             }
 518         }
 519 
 520         string secureUrl;
 521 
 522         /// <summary>
 523         /// Sets or gets the secure (HTTPS) URL to use for the Windows Live
 524         /// Login server.  You should not have to use or change this
 525         /// directly.  
 526         // </summary>
 527         public string SecureUrl
 528         {
 529             set { secureUrl = value; }
 530 
 531             get
 532             {
 533                 if (string.IsNullOrEmpty(secureUrl))
 534                 {
 535                     return "https://login.live.com/";
 536                 }
 537 
 538                 return secureUrl;
 539             }
 540         }
 541 
 542         string consentUrl;
 543 
 544         /// <summary>
 545         /// Sets or gets the URL to use for the Windows Live Consent server. You
 546         /// should not have to use or change this directly.
 547         /// </summary>
 548         public string ConsentUrl
 549         {
 550             set { consentUrl = value; }
 551 
 552             get
 553             {
 554                 if (string.IsNullOrEmpty(consentUrl))
 555                 {
 556                     return "https://consent.live.com/";
 557                 }
 558 
 559                 return consentUrl;
 560             }
 561         }
 562 
 563         /* Methods for Web Authentication support. */
 564 
 565         /// <summary>
 566         /// Returns the sign-in URL to use for the Windows Live Login server.
 567         /// We recommend that you use the Sign In control instead.
 568         /// </summary>
 569         /// <returns>Sign-in URL</returns>
 570         public string GetLoginUrl()
 571         {
 572             return GetLoginUrl(null);
 573         }
 574 
 575         /// <summary>
 576         /// Returns the sign-in URL to use for the Windows Live Login server.
 577         /// We recommend that you use the Sign In control instead.
 578         /// </summary>
 579         /// <param name="context">If you specify it, <paramref
 580         /// name="context"/> will be returned as-is in the sign-in
 581         /// response for site-specific use.</param>
 582         /// <returns>Sign-in URL</returns>
 583         public string GetLoginUrl(string context)
 584         {
 585             return GetLoginUrl(context, null);
 586         }
 587 
 588         /// <summary>
 589         /// Returns the sign-in URL to use for the Windows Live Login server.
 590         /// We recommend that you use the Sign In control instead.
 591         /// </summary>
 592         /// <param name="context">If you specify it, <paramref
 593         /// name="context"/> will be returned as-is in the sign-in
 594         /// response for site-specific use.</param>
 595         /// <param name="market">The language in which the sign-in page is 
 596         /// displayed is configured by culture ID (For example, 'fr-fr' or 
 597         /// 'en-us') specified in the 'market' parameter.</param>
 598         /// <returns>Sign-in URL</returns>
 599         public string GetLoginUrl(string context, string market)
 600         {
 601             string alg = "&alg=" + SecurityAlgorithm;
 602 
 603             context = string.IsNullOrEmpty(context) ?
 604               string.Empty : "&appctx=" + HttpUtility.UrlEncode(context);
 605 
 606             market = string.IsNullOrEmpty(market) ?
 607               string.Empty : "&mkt=" + HttpUtility.UrlEncode(market);
 608 
 609             return BaseUrl + "wlogin.srf?appid=" + AppId +
 610               alg + context + market;
 611         }
 612 
 613         /// <summary>
 614         /// Returns the sign-out URL to use for the Windows Live Login server.
 615         /// We recommend that you use the Sign In control instead.
 616         /// </summary>
 617         /// <returns>Sign-out URL</returns>
 618         public string GetLogoutUrl()
 619         {
 620             return GetLogoutUrl(null);
 621         }
 622 
 623         /// <summary>
 624         /// Returns the sign-out URL to use for the Windows Live Login server.
 625         /// We recommend that you use the Sign In control instead.
 626         /// </summary>
 627         /// <param name="market">The language in which the sign-in page is 
 628         /// displayed is configured by culture ID (For example, 'fr-fr' or 
 629         /// 'en-us') specified in the 'market' parameter.</param>
 630         /// <returns>Sign-out URL</returns>
 631         public string GetLogoutUrl(string market)
 632         {
 633             market = string.IsNullOrEmpty(market) ?
 634               string.Empty : "&mkt=" + HttpUtility.UrlEncode(market);
 635 
 636             return BaseUrl + "logout.srf?appid=" + AppId + market;
 637         }
 638 
 639         /// <summary>
 640         /// Holds the user information after a successful sign-in.
 641         /// </summary>
 642         public class User
 643         {
 644             public User(string timestamp, string id, string flags, string context, string token)
 645             {
 646                 setTimestamp(timestamp);
 647                 setId(id);
 648                 setFlags(flags);
 649                 setContext(context);
 650                 setToken(token);
 651             }
 652 
 653             DateTime timestamp;
 654 
 655             /// <summary>
 656             ///  Returns the timestamp as obtained from the SSO token.
 657             /// </summary>
 658             public DateTime Timestamp { get { return timestamp; } }
 659 
 660             /// <summary>
 661             /// Sets the Unix timestamp.
 662             /// </summary>
 663             /// <param name="timestamp"></param>
 664             private void setTimestamp(string timestamp)
 665             {
 666                 if (string.IsNullOrEmpty(timestamp))
 667                 {
 668                     throw new ArgumentException("Error: User: Null timestamp in token.");
 669                 }
 670 
 671                 int timestampInt;
 672 
 673                 try
 674                 {
 675                     timestampInt = Convert.ToInt32(timestamp);
 676                 }
 677                 catch (Exception)
 678                 {
 679                     throw new ArgumentException("Error: User: Invalid timestamp: "
 680                                                 + timestamp);
 681                 }
 682 
 683                 DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
 684                 this.timestamp = refTime.AddSeconds(timestampInt);
 685             }
 686 
 687             string id;
 688 
 689             /// <summary>
 690             /// Returns the pairwise unique ID for the user.
 691             /// </summary>
 692             public string Id { get { return id; } }
 693 
 694             /// <summary>
 695             /// Sets the pairwise unique ID for the user.
 696             /// </summary>
 697             /// <param name="id">User id</param>
 698             private void setId(string id)
 699             {
 700                 if (string.IsNullOrEmpty(id))
 701                 {
 702                     throw new ArgumentException("Error: User: Null id in token.");
 703                 }
 704 
 705                 Regex re = new Regex(@"^\w+$");
 706                 if (!re.IsMatch(id))
 707                 {
 708                     throw new ArgumentException("Error: User: Invalid id: " + id);
 709                 }
 710 
 711                 this.id = id;
 712             }
 713 
 714             bool usePersistentCookie;
 715 
 716             /// <summary>
 717             /// Indicates whether the application
 718             /// is expected to store the user token in a session or
 719             /// persistent cookie.
 720             /// </summary>
 721             public bool UsePersistentCookie { get { return usePersistentCookie; } }
 722 
 723             /// <summary>
 724             /// Sets the usePersistentCookie flag for the user.
 725             /// </summary>
 726             /// <param name="flags"></param>
 727             private void setFlags(string flags)
 728             {
 729                 this.usePersistentCookie = false;
 730 
 731                 if (!string.IsNullOrEmpty(flags))
 732                 {
 733                     try
 734                     {
 735                         int flagsInt = Convert.ToInt32(flags);
 736                         this.usePersistentCookie = ((flagsInt % 2) == 1);
 737                     }
 738                     catch (Exception)
 739                     {
 740                         throw new ArgumentException("Error: User: Invalid flags: "
 741                                                     + flags);
 742                     }
 743                 }
 744             }
 745 
 746             string context;
 747 
 748             /// <summary>
 749             /// Returns the application context that was originally passed
 750             /// to the sign-in request, if any.
 751             /// </summary>
 752             public string Context { get { return context; } }
 753 
 754             /// <summary>
 755             /// Sets the the Application context.
 756             /// </summary>
 757             /// <param name="context"></param>
 758             private void setContext(string context)
 759             {
 760                 this.context = context;
 761             }
 762 
 763             string token;
 764 
 765             /// <summary>
 766             /// Returns the encrypted Web Authentication token containing 
 767             /// the UID. This can be cached in a cookie and the UID can be
 768             /// retrieved by calling the ProcessToken method.
 769             /// </summary>
 770             public string Token { get { return token; } }
 771 
 772             /// <summary>
 773             /// Sets the the User token.
 774             /// </summary>
 775             /// <param name="token"></param>
 776             private void setToken(string token)
 777             {
 778                 this.token = token;
 779             }
 780         }
 781 
 782         /// <summary>
 783         /// Processes the sign-in response from the Windows Live Login server.
 784         /// </summary>
 785         ///
 786         /// <param name="query">Contains the preprocessed POST query
 787         /// such as that returned by HttpRequest.Form</param>
 788         /// 
 789         /// <returns>The method returns a User object on successful
 790         /// sign-in; otherwise null.</returns>
 791         public User ProcessLogin(NameValueCollection query)
 792         {
 793             if (query == null)
 794             {
 795                 debug("Error: ProcessLogin: Invalid query.");
 796                 return null;
 797             }
 798 
 799             string action = query["action"];
 800 
 801             if (action != "login")
 802             {
 803                 debug("Warning: ProcessLogin: query action ignored: " + action);
 804                 return null;
 805             }
 806 
 807             string token = query["stoken"];
 808             string context = query["appctx"];
 809 
 810             if (context != null)
 811             {
 812                 context = HttpUtility.UrlDecode(context);
 813             }
 814 
 815             return ProcessToken(token, context);
 816         }
 817 
 818         /// <summary>
 819         /// Decodes and validates a Web Authentication token. Returns a User
 820         /// object on success.
 821         /// </summary>
 822         public User ProcessToken(string token)
 823         {
 824             return ProcessToken(token, null);
 825         }
 826 
 827         /// <summary>
 828         /// Decodes and validates a Web Authentication token. Returns a User
 829         /// object on success. If a context is passed in, it will be
 830         /// returned as the context field in the User object.
 831         /// </summary>
 832         /// <param name="token">Web Authentication token</param>
 833         /// <param name="context">If you specify it, <paramref
 834         /// name="context"/> will be returned as-is in the sign-in
 835         /// response for site-specific use.</param>        
 836         /// <returns>User object</returns>
 837         public User ProcessToken(string token, string context)
 838         {
 839             if (string.IsNullOrEmpty(token))
 840             {
 841                 debug("Error: ProcessToken: Invalid token.");
 842                 return null;
 843             }
 844 
 845             string stoken = DecodeAndValidateToken(token);
 846 
 847             if (string.IsNullOrEmpty(stoken))
 848             {
 849                 debug("Error: ProcessToken: Failed to decode/validate token: " +
 850                       token);
 851                 return null;
 852             }
 853 
 854             NameValueCollection parsedToken = parse(stoken);
 855             if (parsedToken == null || parsedToken.Count < 3)
 856             {
 857                 debug("Error: ProcessToken: Failed to parse token after decoding: " +
 858                       token);
 859                 return null;
 860             }
 861 
 862             string appId = parsedToken["appid"];
 863             if (appId != AppId)
 864             {
 865                 debug("Error: ProcessToken: Application ID in token did not match ours: " +
 866                       appId + ", " + AppId);
 867                 return null;
 868             }
 869 
 870             User user = null;
 871             try
 872             {
 873                 user = new User(parsedToken["ts"],
 874                                 parsedToken["uid"],
 875                                 parsedToken["flags"],
 876                                 context, token);
 877             }
 878             catch (Exception e)
 879             {
 880                 debug("Error: ProcessToken: Contents of token considered invalid: " + e);
 881             }
 882             return user;
 883         }
 884 
 885         /// <summary>
 886         /// Returns an appropriate content type and body
 887         /// response that the application handler can return to
 888         /// signify a successful sign-out from the application.
 889         /// 
 890         /// When a user signs out of Windows Live or a Windows Live
 891         /// application, a best-effort attempt is made to sign the user out
 892         /// from all other Windows Live applications the user might be signed
 893         /// in to. This is done by calling the handler page for each
 894         /// application with 'action' parameter set to 'clearcookie' in the query
 895         /// string. The application handler is then responsible for clearing
 896         /// any cookies or data associated with the sign-in. After successfully
 897         /// signing the user out, the handler should return a GIF (any
 898         /// GIF) as response to the action=clearcookie query.
 899         /// </summary>
 900         public void GetClearCookieResponse(out string type, out byte[] content)
 901         {
 902             const string gif =
 903               "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7";
 904             type = "image/gif";
 905             content = Convert.FromBase64String(gif);
 906         }
 907 
 908         /* Methods for Delegated Authentication support. */
 909 
 910         /// <summary>
 911         /// Returns the consent URL to use for Delegated Authentication for
 912         /// the given comma-delimited list of offers.
 913         /// </summary>
 914         /// <param name="offers">Comma-delimited list of offers.</param>
 915         /// <returns>Consent URL</returns>
 916         public string GetConsentUrl(string offers)
 917         {
 918             return GetConsentUrl(offers, null);
 919         }
 920 
 921         /// <summary>
 922         /// Returns the consent URL to use for Delegated Authentication for
 923         /// the given comma-delimited list of offers.
 924         /// </summary>
 925         /// <param name="offers">Comma-delimited list of offers.</param>
 926         /// <param name="context">If you specify it, <paramref
 927         /// name="context"/> will be returned as-is in the consent 
 928         /// response for site-specific use.</param>
 929         /// <returns>Consent URL</returns>
 930         public string GetConsentUrl(string offers, string context)
 931         {
 932             return GetConsentUrl(offers, context, null);
 933         }
 934 
 935         /// <summary>
 936         /// Returns the consent URL to use for Delegated Authentication for
 937         /// the given comma-delimited list of offers.
 938         /// </summary>
 939         /// <param name="offers">Comma-delimited list of offers.</param>
 940         /// <param name="context">If you specify it, <paramref
 941         /// name="context"/> will be returned as-is in the consent 
 942         /// response for site-specific use.</param>
 943         /// <param name="ru">The registered/configured return URL will be 
 944         /// overridden by 'ru' specified here.</param>
 945         /// <returns>Consent URL</returns>
 946         public string GetConsentUrl(string offers, string context, string ru)
 947         {
 948             return GetConsentUrl(offers, context, ru, null);
 949         }
 950 
 951         /// <summary>
 952         /// Returns the consent URL to use for Delegated Authentication for
 953         /// the given comma-delimited list of offers.
 954         /// </summary>
 955         /// <param name="offers">Comma-delimited list of offers.</param>
 956         /// <param name="context">If you specify it, <paramref
 957         /// name="context"/> will be returned as-is in the sign-in
 958         /// response for site-specific use.</param>
 959         /// <param name="ru">The registered/configured return URL will be 
 960         /// overridden by 'ru' specified here.</param>
 961         /// <param name="market">The language in which the consent page is 
 962         /// displayed is configured by culture ID (For example, 'fr-fr' or 
 963         /// 'en-us') specified in the 'market' parameter.</param>
 964         /// <returns>Consent URL</returns>
 965         public string GetConsentUrl(string offers, string context, string ru, string market)
 966         {
 967             if (string.IsNullOrEmpty(offers))
 968             {
 969                 throw new ArgumentException("Error: GetConsentUrl: Invalid offers list.");
 970             }
 971 
 972             offers = "?ps=" + HttpUtility.UrlEncode(offers);
 973 
 974             context = string.IsNullOrEmpty(context) ?
 975               string.Empty : "&appctx=" + HttpUtility.UrlEncode(context);
 976 
 977             if (string.IsNullOrEmpty(ru))
 978             {
 979                 ru = ReturnUrl;
 980             }
 981 
 982             ru = string.IsNullOrEmpty(ru) ?
 983               string.Empty : "&ru=" + HttpUtility.UrlEncode(ru);
 984 
 985             market = string.IsNullOrEmpty(market) ?
 986               string.Empty : "&mkt=" + HttpUtility.UrlEncode(market);
 987 
 988             string pu = string.Empty;
 989 
 990             if (!string.IsNullOrEmpty(PolicyUrl))
 991             {
 992                 pu = "&pl=" + HttpUtility.UrlEncode(PolicyUrl);
 993             }
 994 
 995             string app = string.Empty;
 996 
 997             if (!ForceDelAuthNonProvisioned)
 998             {
 999                 app = "&app=" + GetAppVerifier();
1000             }
1001 
1002             return (ConsentUrl + "Delegation.aspx" + offers + context + ru + pu + market + app);
1003         }
1004 
1005         /// <summary>
1006         /// Returns the URL to use to download a new consent token, given the 
1007         /// offers and refresh token.
1008         /// </summary>
1009         /// <param name="offers">Comma-delimited list of offers.</param>
1010         /// <param name="refreshToken">Refresh token.</param>
1011         /// <returns>Refresh consent token URL</returns>
1012         public string GetRefreshConsentTokenUrl(string offers, string refreshToken)
1013         {
1014             return GetRefreshConsentTokenUrl(offers, refreshToken, null);
1015         }
1016 
1017         /// <summary>
1018         /// Returns the URL to use to download a new consent token, given the 
1019         /// offers and refresh token.
1020         /// </summary>
1021         /// <param name="offers">Comma-delimited list of offers.</param>
1022         /// <param name="refreshToken">Refresh token.</param>
1023         /// <returns>Refresh consent token URL</returns>
1024         /// <param name="ru">The registered/configured return URL will be 
1025         /// overridden by 'ru' specified here.</param>
1026         /// <returns>Refresh consent token URL</returns>
1027         public string GetRefreshConsentTokenUrl(string offers, string refreshToken, string ru)
1028         {
1029             if (string.IsNullOrEmpty(offers))
1030             {
1031                 throw new ArgumentException("Error: GetRefreshConsentTokenUrl: Invalid offers list.");
1032             }
1033 
1034             offers = "?ps=" + HttpUtility.UrlEncode(offers);
1035 
1036             if (string.IsNullOrEmpty(refreshToken))
1037             {
1038                 throw new ArgumentException("Error: GetRefreshConsentTokenUrl: Invalid refresh token.");
1039             }
1040 
1041             refreshToken = "&reft=" + refreshToken;
1042 
1043             if (string.IsNullOrEmpty(ru))
1044             {
1045                 ru = ReturnUrl;
1046             }
1047 
1048             ru = string.IsNullOrEmpty(ru) ?
1049               string.Empty : "&ru=" + HttpUtility.UrlEncode(ru);
1050 
1051             string app = string.Empty;
1052 
1053             if (!ForceDelAuthNonProvisioned)
1054             {
1055                 app = "&app=" + GetAppVerifier();
1056             }
1057 
1058             return ConsentUrl + "RefreshToken.aspx" + offers + refreshToken + ru + app;
1059         }
1060 
1061         /// <summary>
1062         /// Returns the URL for the consent-management user interface.
1063         /// </summary>
1064         /// <returns>Manage consent URL</returns>
1065         public string GetManageConsentUrl()
1066         {
1067             return GetManageConsentUrl(null);
1068         }
1069 
1070         /// <summary>
1071         /// Returns the URL for the consent-management user interface.
1072         /// </summary>
1073         /// <param name="market">The language in which the consent page is 
1074         /// displayed is configured by culture ID (For example, 'fr-fr' or 
1075         /// 'en-us') specified in the 'market' parameter.</param>
1076         /// <returns>Manage consent URL</returns>
1077         public string GetManageConsentUrl(string market)
1078         {
1079             market = string.IsNullOrEmpty(market) ?
1080               string.Empty : "?mkt=" + HttpUtility.UrlEncode(market);
1081 
1082             return ConsentUrl + "ManageConsent.aspx" + market;
1083         }
1084 
1085         /// <summary>
1086         /// Holds the Consent Token object corresponding to consent granted. 
1087         /// </summary>
1088         public class ConsentToken
1089         {
1090             WindowsLiveLogin wll;
1091 
1092             /// <summary>
1093             /// Initialize the ConsentToken.
1094             /// </summary>
1095             /// <param name="wll">WindowsLiveLogin</param>
1096             /// <param name="delegationToken">Delegation token</param>
1097             /// <param name="refreshToken">Refresh token</param>
1098             /// <param name="sessionKey">Session key</param>
1099             /// <param name="expiry">Expiry</param>
1100             /// <param name="offers">Offers</param>
1101             /// <param name="locationID">Location ID</param>
1102             /// <param name="context">Application context</param>
1103             /// <param name="decodedToken">Decoded token</param>
1104             /// <param name="token">Raw token</param>
1105             public ConsentToken(WindowsLiveLogin wll, string delegationToken, string refreshToken, string sessionKey, string expiry, string offers, string locationID, string context, string decodedToken, string token)
1106             {
1107                 this.wll = wll;
1108                 setDelegationToken(delegationToken);
1109                 setRefreshToken(refreshToken);
1110                 setSessionKey(sessionKey);
1111                 setExpiry(expiry);
1112                 setOffers(offers);
1113                 setLocationID(locationID);
1114                 setContext(context);
1115                 setDecodedToken(decodedToken);
1116                 setToken(token);
1117             }
1118 
1119             string delegationToken;
1120 
1121             /// <summary>
1122             /// Gets the Delegation token.
1123             /// </summary>
1124             public string DelegationToken { get { return delegationToken; } }
1125 
1126             /// <summary>
1127             /// Sets the Delegation token.
1128             /// </summary>
1129             /// <param name="delegationToken">Delegation token</param>
1130             private void setDelegationToken(string delegationToken)
1131             {
1132                 if (string.IsNullOrEmpty(delegationToken))
1133                 {
1134                     throw new ArgumentException("Error: ConsentToken: Null delegation token.");
1135                 }
1136 
1137                 this.delegationToken = delegationToken;
1138             }
1139 
1140             string refreshToken;
1141 
1142             /// <summary>
1143             /// Gets the refresh token.
1144             /// </summary>
1145             public string RefreshToken { get { return refreshToken; } }
1146 
1147             /// <summary>
1148             /// Sets the refresh token.
1149             /// </summary>
1150             /// <param name="refreshToken">Refresh token</param>
1151             private void setRefreshToken(string refreshToken)
1152             {
1153                 this.refreshToken = refreshToken;
1154             }
1155 
1156             byte[] sessionKey;
1157 
1158             /// <summary>
1159             /// Gets the session key.
1160             /// </summary>
1161             public byte[] SessionKey { get { return sessionKey; } }
1162 
1163             /// <summary>
1164             /// Sets the session key.
1165             /// </summary>
1166             /// <param name="sessionKey">Session key</param>
1167             private void setSessionKey(string sessionKey)
1168             {
1169                 if (string.IsNullOrEmpty(sessionKey))
1170                 {
1171                     throw new ArgumentException("Error: ConsentToken: Null session key.");
1172                 }
1173 
1174                 this.sessionKey = WindowsLiveLogin.u64(sessionKey);
1175             }
1176 
1177             DateTime expiry;
1178 
1179             /// <summary>
1180             /// Gets the expiry time of delegation token.
1181             /// </summary>
1182             public DateTime Expiry { get { return expiry; } }
1183 
1184             /// <summary>
1185             /// Sets the expiry time of delegation token.
1186             /// </summary>
1187             /// <param name="expiry">Expiry time</param>
1188             private void setExpiry(string expiry)
1189             {
1190                 if (string.IsNullOrEmpty(expiry))
1191                 {
1192                     throw new ArgumentException("Error: ConsentToken: Null expiry time.");
1193                 }
1194 
1195                 int expiryInt;
1196 
1197                 try
1198                 {
1199                     expiryInt = Convert.ToInt32(expiry);
1200                 }
1201                 catch (Exception)
1202                 {
1203                     throw new ArgumentException("Error: Consent: Invalid expiry time: "
1204                                                 + expiry);
1205                 }
1206 
1207                 DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
1208                 this.expiry = refTime.AddSeconds(expiryInt);
1209             }
1210 
1211             IList offers;
1212 
1213             /// <summary>
1214             /// Gets the list of offers/actions for which the user granted consent.
1215             /// </summary>
1216             public IList Offers { get { return offers; } }
1217 
1218             string offersString;
1219 
1220             /// <summary>
1221             /// Gets the string representation of all the offers/actions for which 
1222             /// the user granted consent.
1223             /// </summary>
1224             public String OffersString { get { return offersString; } }
1225 
1226             /// <summary>
1227             /// Sets the offers/actions for which user granted consent.
1228             /// </summary>
1229             /// <param name="offers">Comma-delimited list of offers</param>
1230             private void setOffers(string offers)
1231             {
1232                 if (string.IsNullOrEmpty(offers))
1233                 {
1234                     throw new ArgumentException("Error: ConsentToken: Null offers.");
1235                 }
1236 
1237                 offers = HttpUtility.UrlDecode(offers);
1238 
1239                 this.offersString = string.Empty;
1240                 this.offers = new ArrayList();
1241 
1242                 string[] offersList = offers.Split(new Char[] { ';' });
1243 
1244                 foreach (string offer in offersList)
1245                 {
1246                     if (!(this.offersString == string.Empty))
1247                     {
1248                         this.offersString += ",";
1249                     }
1250 
1251                     int separator = offer.IndexOf(':');
1252                     if (separator == -1)
1253                     {
1254                         debug("Warning: ConsentToken: offer may be invalid: " + offer);
1255                         this.offers.Add(offer);
1256                         this.offersString += offer;
1257                     }
1258                     else
1259                     {
1260                         string o = offer.Substring(0, separator);
1261                         this.offers.Add(o);
1262                         this.offersString += o;
1263                     }
1264                 }
1265             }
1266 
1267             string locationID;
1268 
1269             /// <summary>
1270             /// Gets the location ID.
1271             /// </summary>
1272             public string LocationID { get { return locationID; } }
1273 
1274             /// <summary>
1275             /// Sets the location ID.
1276             /// </summary>
1277             /// <param name="locationID">Location ID</param>
1278             private void setLocationID(string locationID)
1279             {
1280                 this.locationID = locationID;
1281             }
1282 
1283             string context;
1284 
1285             /// <summary>
1286             /// Returns the application context that was originally passed 
1287             /// to the consent request, if any.
1288             /// </summary>
1289             public string Context { get { return context; } }
1290 
1291             /// <summary>
1292             /// Sets the application context.
1293             /// </summary>
1294             /// <param name="context">Application context</param>
1295             private void setContext(string context)
1296             {
1297                 this.context = context;
1298             }
1299 
1300             string decodedToken;
1301 
1302             /// <summary>
1303             /// Gets the decoded token.
1304             /// </summary>
1305             public string DecodedToken { get { return decodedToken; } }
1306 
1307             /// <summary>
1308             /// Sets the decoded token.
1309             /// </summary>
1310             /// <param name="decodedToken">Decoded token</param>
1311             private void setDecodedToken(string decodedToken)
1312             {
1313                 this.decodedToken = decodedToken;
1314             }
1315 
1316             string token;
1317 
1318             /// <summary>
1319             /// Gets the raw token.
1320             /// </summary>
1321             public string Token { get { return token; } }
1322 
1323             /// <summary>
1324             /// Sets the raw token.
1325             /// </summary>
1326             /// <param name="token">Raw token</param>
1327             private void setToken(string token)
1328             {
1329                 this.token = token;
1330             }
1331 
1332             /// <summary>
1333             /// Indicates whether the delegation token is set and has not expired.
1334             /// </summary>
1335             /// <returns></returns>
1336             public bool IsValid()
1337             {
1338                 if (string.IsNullOrEmpty(DelegationToken))
1339                 {
1340                     return false;
1341                 }
1342 
1343                 if (DateTime.UtcNow.AddSeconds(-300) > Expiry)
1344                 {
1345                     return false;
1346                 }
1347 
1348                 return true;
1349             }
1350 
1351             /// <summary>
1352             /// Attempt to refresh the current token and replace it. If operation succeeds 
1353             /// true is returned to signify success.
1354             /// </summary>
1355             /// <returns></returns>
1356             public bool Refresh()
1357             {
1358                 ConsentToken ct = wll.RefreshConsentToken(this);
1359 
1360                 if (ct == null)
1361                 {
1362                     return false;
1363                 }
1364 
1365                 copy(ct);
1366 
1367                 return true;
1368             }
1369 
1370             /// <summary>
1371             /// Makes a copy of the ConsentToken object.
1372             /// </summary>
1373             /// <param name="consentToken"></param>
1374             void copy(ConsentToken consentToken)
1375             {
1376                 this.delegationToken = consentToken.delegationToken;
1377                 this.refreshToken = consentToken.refreshToken;
1378                 this.sessionKey = consentToken.sessionKey;
1379                 this.expiry = consentToken.expiry;
1380                 this.offers = consentToken.offers;
1381                 this.locationID = consentToken.locationID;
1382                 this.offersString = consentToken.offersString;
1383                 this.decodedToken = consentToken.decodedToken;
1384                 this.token = consentToken.token;
1385             }
1386         }
1387 
1388         /// <summary>
1389         /// Processes the POST response from the Delegated Authentication 
1390         /// service after a user has granted consent. The processConsent
1391         /// function extracts the consent token string and returns the result 
1392         /// of invoking the processConsentToken method. 
1393         /// </summary>
1394         /// <param name="query">Response from the Delegated Authentication service.</param>
1395         /// <returns>ConsentToken</returns>
1396         public ConsentToken ProcessConsent(NameValueCollection query)
1397         {
1398             if (query == null)
1399             {
1400                 debug("Error: ProcessConsent: Invalid query.");
1401                 return null;
1402             }
1403 
1404             string action = query["action"];
1405 
1406             if (action != "delauth")
1407             {
1408                 debug("Warning: ProcessConsent: query action ignored: " + action);
1409                 return null;
1410             }
1411 
1412             if (query["ResponseCode"] != "RequestApproved")
1413             {
1414                 debug("Error: ProcessConsent: Consent was not successfully granted: "
1415                       + query["ResponseCode"]);
1416                 return null;
1417             }
1418 
1419             string token = query["ConsentToken"];
1420             string context = query["appctx"];
1421 
1422             if (!string.IsNullOrEmpty(context))
1423             {
1424                 context = HttpUtility.UrlDecode(context);
1425             }
1426 
1427             return ProcessConsentToken(token, context);
1428         }
1429 
1430         /// <summary>
1431         /// Processes the consent token string that is returned in the POST 
1432         /// response by the Delegated Authentication service after a 
1433         /// user has granted consent.
1434         /// </summary>
1435         /// <param name="token">Raw token.</param>
1436         /// <returns>ConsentToken</returns>
1437         public ConsentToken ProcessConsentToken(string token)
1438         {
1439             return ProcessConsentToken(token, null);
1440         }
1441 
1442         /// <summary>
1443         /// Processes the consent token string that is returned in the POST 
1444         /// response by the Delegated Authentication service after a 
1445         /// user has granted consent.
1446         /// </summary>
1447         /// <param name="token">Raw token.</param>
1448         /// <param name="context">If you specify it, <paramref
1449         /// name="context"/> will be returned as-is in the sign-in
1450         /// response for site-specific use.</param>
1451         /// <returns></returns>
1452         public ConsentToken ProcessConsentToken(string token, string context)
1453         {
1454             string decodedToken = token;
1455 
1456             if (string.IsNullOrEmpty(token))
1457             {
1458                 debug("Error: ProcessConsentToken: Null token.");
1459                 return null;
1460             }
1461 
1462             NameValueCollection parsedToken =
1463               parse(HttpUtility.UrlDecode(token));
1464 
1465             if (!string.IsNullOrEmpty(parsedToken["eact"]))
1466             {
1467                 decodedToken = DecodeAndValidateToken(parsedToken["eact"]);
1468                 if (string.IsNullOrEmpty(decodedToken))
1469                 {
1470                     debug("Error: ProcessConsentToken: Failed to decode/validate token: " +
1471                           token);
1472                     return null;
1473                 }
1474 
1475                 parsedToken = parse(decodedToken);
1476                 decodedToken = HttpUtility.UrlEncode(decodedToken);
1477             }
1478 
1479             ConsentToken consentToken = null;
1480             try
1481             {
1482                 consentToken = new ConsentToken(this,
1483                                                 parsedToken["delt"],
1484                                                 parsedToken["reft"],
1485                                                 parsedToken["skey"],
1486                                                 parsedToken["exp"],
1487                                                 parsedToken["offer"],
1488                                                 parsedToken["lid"],
1489                                                 context, decodedToken,
1490                                                 token);
1491             }
1492             catch (Exception e)
1493             {
1494                 debug("Error: ProcessConsentToken: Contents of token considered invalid: " + e);
1495             }
1496 
1497             return consentToken;
1498         }
1499 
1500         /// <summary>
1501         /// Attempts to obtain a new, refreshed token and return it. The 
1502         /// original token is not modified.
1503         /// </summary>
1504         /// <param name="token">ConsentToken object.</param>
1505         /// <returns>Refreshed ConsentToken object.</returns>
1506         public ConsentToken RefreshConsentToken(ConsentToken token)
1507         {
1508             return RefreshConsentToken(token, null);
1509         }
1510 
1511         /// <summary>
1512         /// Attempts to obtain a new, refreshed token and return it. The 
1513         /// original token is not modified.
1514         /// </summary>
1515         /// <param name="token">ConsentToken object.</param>
1516         /// <param name="ru">The registered/configured return URL will be 
1517         /// overridden by 'ru' specified here.</param>
1518         /// <returns>Refreshed ConsentToken object.</returns>
1519         public ConsentToken RefreshConsentToken(ConsentToken token, string ru)
1520         {
1521             if (token == null)
1522             {
1523                 debug("Error: RefreshConsentToken: Null consent token.");
1524                 return null;
1525             }
1526 
1527             return RefreshConsentToken(token.OffersString, token.RefreshToken, ru);
1528         }
1529 
1530         /// <summary>
1531         /// Attempts to obtain a new, refreshed token and return it using 
1532         /// the offers and refresh token. The original token is not modified.
1533         /// </summary>
1534         /// <param name="offers">Comma-delimited list of offers.</param>
1535         /// <param name="refreshToken">Refresh token.</param>
1536         /// <returns>Refreshed ConsentToken object.</returns>
1537         public ConsentToken RefreshConsentToken(string offers, string refreshToken)
1538         {
1539             return RefreshConsentToken(offers, refreshToken, null);
1540         }
1541 
1542         /// <summary>
1543         /// Attempts to obtain a new, refreshed token and return it using 
1544         /// the offers and refresh token. The original token is not modified.
1545         /// </summary>
1546         /// <param name="offers">Comma-delimited list of offers.</param>
1547         /// <param name="refreshToken">Refresh token.</param>
1548         /// <param name="ru">The registered/configured return URL will be 
1549         /// overridden by 'ru' specified here.</param>
1550         /// <returns>Refreshed ConsentToken object.</returns>
1551         public ConsentToken RefreshConsentToken(string offers, string refreshToken, string ru)
1552         {
1553             string url = null;
1554 
1555             try
1556             {
1557                 url = GetRefreshConsentTokenUrl(offers, refreshToken, ru);
1558             }
1559             catch (Exception e)
1560             {
1561                 debug("Error: Failed to construct refresh consent token URL: " + e);
1562                 return null;
1563             }
1564 
1565             if (string.IsNullOrEmpty(url))
1566             {
1567                 debug("Error: Failed to construct refresh consent token URL.");
1568                 return null;
1569             }
1570 
1571             string body = fetch(url);
1572 
1573             if (string.IsNullOrEmpty(body))
1574             {
1575                 debug("Error: RefreshConsentToken: Failed to download token.");
1576                 return null;
1577             }
1578 
1579             Regex re = new Regex("{\"ConsentToken\":\"(.*)\"}");
1580             GroupCollection gc = re.Match(body).Groups;
1581 
1582             if (gc.Count != 2)
1583             {
1584                 debug("Error: RefreshConsentToken: Failed to extract token: " + body);
1585                 return null;
1586             }
1587 
1588             CaptureCollection cc = gc[1].Captures;
1589 
1590             if (cc.Count != 1)
1591             {
1592                 debug("Error: RefreshConsentToken: Failed to extract token: " + body);
1593                 return null;
1594             }
1595 
1596             return ProcessConsentToken(cc[0].ToString());
1597         }
1598 
1599         /* Common methods. */
1600 
1601         /// <summary>
1602         /// Decodes and validates the raw token.
1603         /// </summary>
1604         /// <param name="token"></param>
1605         /// <returns></returns>
1606         public string DecodeAndValidateToken(string token)
1607         {
1608             bool haveOldSecret = false;
1609 
1610             if ((oldSecretExpiry != null) && (DateTime.UtcNow < oldSecretExpiry))
1611             {
1612                 if ((oldCryptKey != null) && (oldSignKey != null))
1613                 {
1614                     haveOldSecret = true;
1615                 }
1616             }
1617 
1618             string stoken = DecodeAndValidateToken(token, cryptKey, signKey);
1619 
1620             if (string.IsNullOrEmpty(stoken))
1621             {
1622                 if (haveOldSecret)
1623                 {
1624                     debug("Warning: Failed to validate token with current secret, attempting old secret.");
1625                     return DecodeAndValidateToken(token, oldCryptKey, oldSignKey);
1626                 }
1627             }
1628 
1629             return stoken;
1630         }
1631 
1632         /// <summary>
1633         /// Decodes and validates the raw token with appropriate crypt key 
1634         /// and sign key.
1635         /// </summary>
1636         /// <param name="token">Raw token.</param>
1637         /// <param name="cryptKey">Crypt key.</param>
1638         /// <param name="signKey">Sign key.</param>
1639         /// <returns></returns>
1640         public string DecodeAndValidateToken(string token, byte[] cryptKey, byte[] signKey)
1641         {
1642             string stoken = DecodeToken(token, cryptKey);
1643 
1644             if (!string.IsNullOrEmpty(stoken))
1645             {
1646                 stoken = ValidateToken(stoken, signKey);
1647             }
1648 
1649             return stoken;
1650         }
1651 
1652         /// <summary>
1653         /// Decode the given token. Returns null on failure.
1654         /// </summary>
1655         ///
1656         /// <list type="number">
1657         /// <item>First, the string is URL unescaped and base64
1658         /// decoded.</item>
1659         /// <item>Second, the IV is extracted from the first 16 bytes
1660         /// of the string.</item>
1661         /// <item>Finally, the string is decrypted by using the
1662         /// encryption key.</item> 
1663         /// </list>
1664         /// <param name="token">Raw token.</param>
1665         /// <returns>Decoded token.</returns>
1666         public string DecodeToken(string token)
1667         {
1668             return DecodeToken(token, cryptKey);
1669         }
1670 
1671         /// <summary>
1672         /// Decode the given token. Returns null on failure.
1673         /// </summary>
1674         ///
1675         /// <list type="number">
1676         /// <item>First, the string is URL unescaped and base64
1677         /// decoded.</item>
1678         /// <item>Second, the IV is extracted from the first 16 bytes
1679         /// of the string.</item>
1680         /// <item>Finally, the string is decrypted by using the
1681         /// encryption key.</item> 
1682         /// </list>
1683         /// <param name="token">Raw token.</param>
1684         /// <param name="cryptKey">Crypt key.</param>
1685         /// <returns>Decoded token.</returns>
1686         public string DecodeToken(string token, byte[] cryptKey)
1687         {
1688             if (cryptKey == null || cryptKey.Length == 0)
1689             {
1690                 throw new InvalidOperationException("Error: DecodeToken: Secret key was not set. Aborting.");
1691             }
1692 
1693             if (string.IsNullOrEmpty(token))
1694             {
1695                 debug("Error: DecodeToken: Null token input.");
1696                 return null;
1697             }
1698 
1699             const int ivLength = 16;
1700             byte[] ivAndEncryptedValue = u64(token);
1701 
1702             if ((ivAndEncryptedValue == null) ||
1703                 (ivAndEncryptedValue.Length <= ivLength) ||
1704                 ((ivAndEncryptedValue.Length % ivLength) != 0))
1705             {
1706                 debug("Error: DecodeToken: Attempted to decode invalid token.");
1707                 return null;
1708             }
1709 
1710             Rijndael aesAlg = null;
1711             MemoryStream memStream = null;
1712             CryptoStream cStream = null;
1713             StreamReader sReader = null;
1714             string decodedValue = null;
1715 
1716             try
1717             {
1718                 aesAlg = new RijndaelManaged();
1719                 aesAlg.KeySize = 128;
1720                 aesAlg.Key = cryptKey;
1721                 aesAlg.Padding = PaddingMode.PKCS7;
1722                 memStream = new MemoryStream(ivAndEncryptedValue);
1723                 byte[] iv = new byte[ivLength];
1724                 memStream.Read(iv, 0, ivLength);
1725                 aesAlg.IV = iv;
1726                 cStream = new CryptoStream(memStream, aesAlg.CreateDecryptor(), CryptoStreamMode.Read);
1727                 sReader = new StreamReader(cStream, Encoding.ASCII);
1728                 decodedValue = sReader.ReadToEnd();
1729             }
1730             catch (Exception e)
1731             {
1732                 debug("Error: DecodeToken: Decryption failed: " + e);
1733                 return null;
1734             }
1735             finally
1736             {
1737                 try
1738                 {
1739                     if (sReader != null) { sReader.Close(); }
1740                     if (cStream != null) { cStream.Close(); }
1741                     if (memStream != null) { memStream.Close(); }
1742                     if (aesAlg != null) { aesAlg.Clear(); }
1743                 }
1744                 catch (Exception e)
1745                 {
1746                     debug("Error: DecodeToken: Failure during resource cleanup: " + e);
1747                 }
1748             }
1749 
1750             return decodedValue;
1751         }
1752 
1753         /// <summary>
1754         /// Creates a signature for the given string.
1755         /// </summary>
1756         public byte[] SignToken(string token)
1757         {
1758             return SignToken(token, signKey);
1759         }
1760 
1761         /// <summary>
1762         /// Creates a signature for the given string by using the
1763         /// signature key.
1764         /// </summary>
1765         public byte[] SignToken(string token, byte[] signKey)
1766         {
1767             if (signKey == null || signKey.Length == 0)
1768             {
1769                 throw new InvalidOperationException("Error: SignToken: Secret key was not set. Aborting.");
1770             }
1771 
1772             if (string.IsNullOrEmpty(token))
1773             {
1774                 debug("Attempted to sign null token.");
1775                 return null;
1776             }
1777 
1778             using (HashAlgorithm hashAlg = new HMACSHA256(signKey))
1779             {
1780                 byte[] data = Encoding.Default.GetBytes(token);
1781                 byte[] hash = hashAlg.ComputeHash(data);
1782                 return hash;
1783             }
1784         }
1785 
1786         /// <summary>
1787         /// Extracts the signature from the token and validates it.
1788         /// </summary>
1789         /// <param name="token"></param>
1790         /// <returns></returns>
1791         public string ValidateToken(string token)
1792         {
1793             return ValidateToken(token, signKey);
1794         }
1795 
1796         /// <summary>
1797         /// Extracts the signature from the token and validates it by using the 
1798         /// signature key.
1799         /// </summary>
1800         public string ValidateToken(string token, byte[] signKey)
1801         {
1802             if (string.IsNullOrEmpty(token))
1803             {
1804                 debug("Error: ValidateToken: Null token.");
1805                 return null;
1806             }
1807 
1808             string[] s = { "&sig=" };
1809             string[] bodyAndSig = token.Split(s, StringSplitOptions.None);
1810 
1811             if (bodyAndSig.Length != 2)
1812             {
1813                 debug("Error: ValidateToken: Invalid token: " + token);
1814                 return null;
1815             }
1816 
1817             byte[] sig = u64(bodyAndSig[1]);
1818 
1819             if (sig == null)
1820             {
1821                 debug("Error: ValidateToken: Could not extract the signature from the token.");
1822                 return null;
1823             }
1824 
1825             byte[] sig2 = SignToken(bodyAndSig[0], signKey);
1826 
1827             if (sig2 == null)
1828             {
1829                 debug("Error: ValidateToken: Could not generate a signature for the token.");
1830                 return null;
1831             }
1832 
1833             if (sig.Length == sig2.Length)
1834             {
1835                 for (int i = 0; i < sig.Length; i++)
1836                 {
1837                     if (sig[i] != sig2[i]) { goto badSig; }
1838                 }
1839 
1840                 return token;
1841             }
1842 
1843         badSig:
1844             debug("Error: ValidateToken: Signature did not match.");
1845             return null;
1846         }
1847 
1848         /* Implementation of the methods needed to perform Windows Live
1849            application verification as well as trusted sign-in. */
1850 
1851         /// <summary>
1852         /// Generates an Application Verifier token.
1853         /// </summary>
1854         public string GetAppVerifier()
1855         {
1856             return GetAppVerifier(null);
1857         }
1858 
1859         /// <summary>
1860         /// Generates an Application Verifier token. An IP address
1861         /// can be included in the token.
1862         /// </summary>
1863         public string GetAppVerifier(string ip)
1864         {
1865             ip = string.IsNullOrEmpty(ip) ? string.Empty : ("&ip=" + ip);
1866             string token = "appid=" + AppId + "&ts=" + getTimestamp() + ip;
1867             string sig = e64(SignToken(token));
1868 
1869             if (string.IsNullOrEmpty(sig))
1870             {
1871                 debug("Error: GetAppVerifier: Failed to sign the token.");
1872                 return null;
1873             }
1874 
1875             token += "&sig=" + sig;
1876             return HttpUtility.UrlEncode(token);
1877         }
1878 
1879         /// <summary>
1880         /// Returns the URL needed to retrieve the application
1881         /// security token. The application security token
1882         /// will be generated for the Windows Live site.
1883         ///
1884         /// JavaScript Output Notation (JSON) output is returned:
1885         ///
1886         /// {"token":"&lt;value&gt;"}
1887         /// </summary>
1888         public string GetAppLoginUrl()
1889         {
1890             return GetAppLoginUrl(null, null, false);
1891         }
1892 
1893         /// <summary>
1894         /// Returns the URL needed to retrieve the application
1895         /// security token.
1896         ///
1897         /// By default, the application security token will be
1898         /// generated for the Windows Live site; a specific Site ID
1899         /// can optionally be specified in 'siteId'.
1900         ///
1901         /// JSON output is returned:
1902         ///
1903         /// {"token":"&lt;value&gt;"}
1904         /// </summary>
1905         public string GetAppLoginUrl(string siteId)
1906         {
1907             return GetAppLoginUrl(siteId, null, false);
1908         }
1909 
1910         /// <summary>
1911         /// Returns the URL needed to retrieve the application
1912         /// security token.
1913         ///
1914         /// By default, the application security token will be
1915         /// generated for the Windows Live site; a specific Site ID
1916         /// can optionally be specified in 'siteId'. The IP address
1917         /// can also optionally be included in 'ip'.
1918         ///
1919         /// JSON output is returned:
1920         ///
1921         /// {"token":"&lt;value&gt;"}
1922         /// </summary>
1923         public string GetAppLoginUrl(string siteId, string ip)
1924         {
1925             return GetAppLoginUrl(siteId, ip, false);
1926         }
1927 
1928         /// <summary>
1929         /// Returns the URL needed to retrieve the application
1930         /// security token.
1931         ///
1932         /// By default, the application security token will be
1933         /// generated for the Windows Live site; a specific Site ID
1934         /// can optionally be specified in 'siteId'. The IP address
1935         /// can also optionally be included in 'ip'.
1936         ///
1937         /// If 'js' is false, then JSON output is returned: 
1938         ///
1939         /// {"token":"&lt;value&gt;"}
1940         ///
1941         /// Otherwise, a JavaScript response is returned. It is assumed
1942         /// that WLIDResultCallback is a custom function implemented to
1943         /// handle the token value:
1944         /// 
1945         /// WLIDResultCallback("&lt;tokenvalue&gt;");
1946         /// </summary>
1947         public string GetAppLoginUrl(string siteId, string ip, bool js)
1948         {
1949             string algPart = "&alg=" + SecurityAlgorithm;
1950             string sitePart = string.IsNullOrEmpty(siteId) ?
1951               string.Empty : "&id=" + siteId;
1952             string jsPart = (!js) ? string.Empty : "&js=1";
1953             string url = SecureUrl + "wapplogin.srf?app=" +
1954               GetAppVerifier(ip) + algPart + sitePart + jsPart;
1955             return url;
1956         }
1957 
1958         /// <summary>
1959         /// Retrieves the application security token for application
1960         /// verification from the application sign-in URL. The
1961         /// application security token will be generated for the
1962         /// Windows Live site.
1963         /// </summary>
1964         public string GetAppSecurityToken()
1965         {
1966             return GetAppSecurityToken(null, null);
1967         }
1968 
1969         /// <summary>
1970         /// Retrieves the application security token for application
1971         /// verification from the application sign-in URL.
1972         ///
1973         /// By default, the application security token will be
1974         /// generated for the Windows Live site; a specific Site ID
1975         /// can optionally be specified in 'siteId'.
1976         /// </summary>
1977         public string GetAppSecurityToken(string siteId)
1978         {
1979             return GetAppSecurityToken(siteId, null);
1980         }
1981 
1982         /// <summary>
1983         /// Retrieves the application security token for application
1984         /// verification from the application sign-in URL.
1985         ///
1986         /// By default, the application security token will be
1987         /// generated for the Windows Live site; a specific Site ID
1988         /// can optionally be specified in 'siteId'. The IP address
1989         /// can also optionally be included in 'ip'.
1990         ///
1991         /// Implementation note: The application security token is
1992         /// downloaded from the application sign-in URL in JSON format
1993         /// {"token":"&lt;value&gt;"}, so we need to extract
1994         /// &lt;value&gt; from the string and return it as seen here.
1995         /// </summary>
1996         public string GetAppSecurityToken(string siteId, string ip)
1997         {
1998             string url = GetAppLoginUrl(siteId, ip);
1999             string body = fetch(url);
2000             if (string.IsNullOrEmpty(body))
2001             {
2002                 debug("Error: GetAppSecurityToken: Failed to download token.");
2003                 return null;
2004             }
2005 
2006             Regex re = new Regex("{\"token\":\"(.*)\"}");
2007             GroupCollection gc = re.Match(body).Groups;
2008 
2009             if (gc.Count != 2)
2010             {
2011                 debug("Error: GetAppSecurityToken: Failed to extract token: " + body);
2012                 return null;
2013             }
2014 
2015             CaptureCollection cc = gc[1].Captures;
2016 
2017             if (cc.Count != 1)
2018             {
2019                 debug("Error: GetAppSecurityToken: Failed to extract token: " + body);
2020                 return null;
2021             }
2022 
2023             return cc[0].ToString();
2024         }
2025 
2026         /// <summary>
2027         /// Returns a string that can be passed to the GetTrustedParams
2028         /// function as the 'retcode' parameter. If this is specified as
2029         /// the 'retcode', then the app will be used as return URL
2030         /// after it finishes trusted sign-in.  
2031         /// </summary>
2032         public string GetAppRetCode()
2033         {
2034             return "appid=" + AppId;
2035         }
2036 
2037         /// <summary>
2038         /// Returns a table of key-value pairs that must be posted to
2039         /// the sign-in URL for trusted sign-in. Use HTTP POST to do
2040         /// this. Be aware that the values in the table are neither
2041         /// URL nor HTML escaped and may have to be escaped if you are
2042         /// inserting them in code such as an HTML form.
2043         /// 
2044         /// The user to be trusted on the local site is passed in as
2045         /// string 'user'.
2046         /// </summary>
2047         public NameValueCollection GetTrustedParams(string user)
2048         {
2049             return GetTrustedParams(user, null);
2050         }
2051 
2052         /// <summary>
2053         /// Returns a table of key-value pairs that must be posted to
2054         /// the sign-in URL for trusted sign-in. Use HTTP POST to do
2055         /// this. Be aware that the values in the table are neither
2056         /// URL nor HTML escaped and may have to be escaped if you are
2057         /// inserting them in code such as an HTML form.
2058         /// 
2059         /// The user to be trusted on the local site is passed in as
2060         /// string 'user'.
2061         /// 
2062         /// Optionally, 'retcode' specifies the resource to which
2063         /// successful sign-in is redirected, such as Windows Live Mail,
2064         /// and is typically a string in the format 'id=2000'. If you
2065         /// pass in the value from GetAppRetCode instead, sign-in will
2066         /// be redirected to the application. Otherwise, an HTTP 200
2067         /// response is returned.
2068         /// </summary>
2069         public NameValueCollection GetTrustedParams(string user, string retcode)
2070         {
2071             string token = GetTrustedToken(user);
2072 
2073             if (string.IsNullOrEmpty(token)) { return null; }
2074 
2075             token = "<wst:RequestSecurityTokenResponse xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\"><wst:RequestedSecurityToken><wsse:BinarySecurityToken xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">" + token + "</wsse:BinarySecurityToken></wst:RequestedSecurityToken><wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\"><wsa:EndpointReference xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\"><wsa:Address>uri:WindowsLiveID</wsa:Address></wsa:EndpointReference></wsp:AppliesTo></wst:RequestSecurityTokenResponse>";
2076 
2077             NameValueCollection nvc = new NameValueCollection(3);
2078             nvc["wa"] = SecurityAlgorithm;
2079             nvc["wresult"] = token;
2080 
2081             if (retcode != null)
2082             {
2083                 nvc["wctx"] = retcode;
2084             }
2085 
2086             return nvc;
2087         }
2088 
2089         /// <summary>
2090         /// Returns the trusted sign-in token in the format needed by the
2091         /// trusted sign-in gadget.
2092         ///
2093         /// User to be trusted on the local site is passed in as string
2094         /// 'user'.
2095         /// </summary>
2096         public string GetTrustedToken(string user)
2097         {
2098             if (string.IsNullOrEmpty(user))
2099             {
2100                 debug("Error: GetTrustedToken: Invalid user specified.");
2101                 return null;
2102             }
2103 
2104             string token = "appid=" + AppId + "&uid=" +
2105               HttpUtility.UrlEncode(user) + "&ts=" + getTimestamp();
2106             string sig = e64(SignToken(token));
2107 
2108             if (string.IsNullOrEmpty(sig))
2109             {
2110                 debug("Error: GetTrustedToken: Failed to sign the token.");
2111                 return null;
2112             }
2113 
2114             token += "&sig=" + sig;
2115             return HttpUtility.UrlEncode(token);
2116         }
2117 
2118         /// <summary>
2119         /// Returns the trusted sign-in URL to use for the Windows Live
2120         /// Login server. 
2121         /// </summary>
2122         public string GetTrustedLoginUrl()
2123         {
2124             return SecureUrl + "wlogin.srf";
2125         }
2126 
2127         /// <summary>
2128         /// Returns the trusted sign-out URL to use for the Windows Live
2129         /// Login server. 
2130         /// </summary>
2131         public string GetTrustedLogoutUrl()
2132         {
2133             return SecureUrl + "logout.srf?appid=" + AppId;
2134         }
2135 
2136         /* Helper methods */
2137 
2138         /// <summary>
2139         /// Function to parse the settings file.
2140         /// </summary>
2141         /// <param name="settingsFile"></param>
2142         /// <returns></returns>
2143         static NameValueCollection parseSettings(string settingsFile)
2144         {
2145             if (string.IsNullOrEmpty(settingsFile))
2146             {
2147                 throw new ArgumentNullException("settingsFile");
2148             }
2149 
2150             // Throws an exception on any failure.
2151             XmlDocument xd = new XmlDocument();
2152             xd.Load(settingsFile);
2153 
2154             XmlNode topNode = xd.SelectSingleNode("//windowslivelogin");
2155 
2156             if (topNode == null)
2157             {
2158                 throw new XmlException("Error: parseSettings: Failed to parse settings file: " + settingsFile);
2159             }
2160 
2161             NameValueCollection settings = new NameValueCollection();
2162             IEnumerator children = topNode.GetEnumerator();
2163 
2164             while (children.MoveNext())
2165             {
2166                 XmlNode child = (XmlNode)children.Current;
2167                 settings[child.Name] = child.InnerText;
2168             }
2169 
2170             return settings;
2171         }
2172 
2173         /// <summary>
2174         /// Derives the key, given the secret key and prefix as described in the 
2175         /// Web Authentication SDK documentation.
2176         /// </summary>
2177         static byte[] derive(string secret, string prefix)
2178         {
2179             using (HashAlgorithm hashAlg = HashAlgorithm.Create("SHA256"))
2180             {
2181                 const int keyLength = 16;
2182                 byte[] data = Encoding.Default.GetBytes(prefix + secret);
2183                 byte[] hashOutput = hashAlg.ComputeHash(data);
2184                 byte[] byteKey = new byte[keyLength];
2185                 Array.Copy(hashOutput, byteKey, keyLength);
2186                 return byteKey;
2187             }
2188         }
2189 
2190         /// <summary>
2191         /// Parses query string and return a table representation of 
2192         /// the key and value pairs.  Similar to 
2193         /// HttpUtility.ParseQueryString, except that no URL decoding
2194         /// is done and only the last value is considered in the case
2195         /// of multiple values with one key.
2196         /// </summary>
2197         static NameValueCollection parse(string input)
2198         {
2199             if (string.IsNullOrEmpty(input))
2200             {
2201                 debug("Error: parse: Null input.");
2202                 return null;
2203             }
2204 
2205             NameValueCollection pairs = new NameValueCollection();
2206 
2207             string[] kvs = input.Split(new Char[] { '&' });
2208             foreach (string kv in kvs)
2209             {
2210                 int separator = kv.IndexOf('=');
2211 
2212                 if ((separator == -1) || (separator == kv.Length))
2213                 {
2214                     debug("Warning: parse: Ignoring pair: " + kv);
2215                     continue;
2216                 }
2217 
2218                 pairs[kv.Substring(0, separator)] = kv.Substring(separator + 1);
2219             }
2220 
2221             return pairs;
2222         }
2223 
2224         /// <summary>
2225         /// Generates a timestamp suitable for the application
2226         /// verifier token.
2227         /// </summary>
2228         static string getTimestamp()
2229         {
2230             DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
2231             TimeSpan ts = DateTime.UtcNow - refTime;
2232             return ((uint)ts.TotalSeconds).ToString();
2233         }
2234 
2235         /// <summary>
2236         /// Base64-encodes and URL-escapes a byte array.
2237         /// </summary>
2238         static string e64(byte[] b)
2239         {
2240             string s = null;
2241             if (b == null) { return s; }
2242 
2243             try
2244             {
2245                 s = Convert.ToBase64String(b);
2246                 s = HttpUtility.UrlEncode(s);
2247             }
2248             catch (Exception e)
2249             {
2250                 debug("Error: e64: Base64 conversion error: " + e);
2251             }
2252 
2253             return s;
2254         }
2255 
2256         /// <summary>
2257         /// URL-unescapes and Base64-decodes a string.
2258         /// </summary>
2259         static byte[] u64(string s)
2260         {
2261             byte[] b = null;
2262             if (s == null) { return b; }
2263             s = HttpUtility.UrlDecode(s);
2264 
2265             try
2266             {
2267                 b = Convert.FromBase64String(s);
2268             }
2269             catch (Exception e)
2270             {
2271                 debug("Error: u64: Base64 conversion error: " + s + ", " + e);
2272             }
2273             return b;
2274         }
2275 
2276         /// <summary>
2277         /// Fetches the contents given a URL.
2278         /// </summary>
2279         static string fetch(string url)
2280         {
2281             string body = null;
2282             try
2283             {
2284                 WebRequest req = HttpWebRequest.Create(url);
2285                 req.Method = "GET";
2286                 WebResponse res = req.GetResponse();
2287                 using (StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8))
2288                 {
2289                     body = sr.ReadToEnd();
2290                 }
2291             }
2292             catch (Exception e)
2293             {
2294                 debug("Error: fetch: Failed to get the document: " + url +
2295                       ", " + e);
2296             }
2297             return body;
2298         }
2299     }
2300 }
View Code

其中用的核心类库,是微软提供的开源类库,可以直接用

 1 try
 2             {
 3                 WindowsLiveLogin wll = new WindowsLiveLogin(true);
 4                 string sonsentUrl = wll.GetConsentUrl("Contacts.View");
 5                 return Content(sonsentUrl);
 6             }
 7             catch (Exception ex)
 8             {
 9                 log.Debug(ex.StackTrace + ex.Message);
10             }
View Code

登出的方法

 1 try
 2             {
 3                 WindowsLiveLogin wll = new WindowsLiveLogin(true);
 4                 string sonsentUrl = string.Format("https://login.live.com/oauth20_logout.srf?client_id={0}&redirect_uri={1}", wll.AppId, appSettings["return_mid_url"]);
 5                
 6                 // 跳转到logouturl之后 要移除cookieRPSSecAuth 
 7                 return Content(sonsentUrl);
 8             }
 9             catch (Exception ex)
10             {
11                 log.Debug(ex.StackTrace + ex.Message);
12             }
13             return Content("error");
View Code

核心获取msn联系人列表的方法

 1 ContactList list = new ContactList();
 2             try
 3             {
 4                 // Construct the request URI.
 5                 string uri = "https://livecontacts.services.live.com/users/@L@" + Cache.Get("glid") + "/rest/LiveContacts/Contacts/";
 6                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
 7                 request.UserAgent = "Windows Live Data Interactive SDK";
 8                 request.ContentType = "application/xml; charset=utf-8";
 9                 request.Method = "GET";
10 
11                 // Add the delegation token to a request header.
12                 request.Headers.Add("Authorization", "DelegatedToken dt=\"" + Cache.Get("gtoken") + "\"");
13 
14                 // Issue the HTTP GET request to Windows Live Contacts.
15                 HttpWebResponse response = (HttpWebResponse)request.GetResponse();
16 
17                 // The response body is an XML stream. Read the stream into an XmlDocument.
18                 XmlDocument contacts = new XmlDocument();
19                 contacts.LoadXml(new StreamReader(response.GetResponseStream()).ReadToEnd());
20                 list.BaseResult.Code = 0;
21                 list.BaseResult.Message = GetContactDataFromXmlM(contacts);
22             }
23             catch (Exception ex) 
24             {
25                 list.BaseResult.Code = 300;
26                 list.BaseResult.Message = ex.StackTrace + ex.Message;
27                 log.Debug(ex.StackTrace + ex.Message);
28             }
29             return list;
View Code

这里返回的是xml格式的数据,我们可以直接解析,也可以返回到前台(转换为json格式),我选择的是后者

 1 for (var i = 0; i < xmlContact.length; i++) {
 2         try {
 3             if (isContain(xmlContact[i].WindowsLiveID)) {
 4                 continue;
 5             }
 6             var person = {};
 7             person["Email"] = xmlContact[i].WindowsLiveID;
 8             if (person["Email"] == "" || person["Email"] == null || person["Email"] == undefined) {
 9                 person["Email"] = xmlContact[i].Emails.Email.Address;
10             }
11 
12             if (xmlContact[i].Profiles.Personal.FirstName && xmlContact[i].Profiles.Personal.LastName) {
13                 person["Name"] = xmlContact[i].Profiles.Personal.FirstName + xmlContact[i].Profiles.Personal.LastName;
14             }
15             else {
16                 person["Name"] = xmlContact[i].Profiles.Personal.DisplayName;
17             }
18             contatListMsn.push(person);
19         }
20         catch (e) {
21             person["Email"] = "";
22             if (xmlContact[i].Profiles.Personal.FirstName && xmlContact[i].Profiles.Personal.LastName) {
23                 person["Name"] = xmlContact[i].Profiles.Personal.FirstName + xmlContact[i].Profiles.Personal.LastName;
24             }
25             else {
26                 person["Name"] = xmlContact[i].Profiles.Personal.DisplayName;
27             }
28             contatListMsn.push(person);
29         }
30         
31     }
View Code

最后,绑定到页面显示,齐活

转载于:https://www.cnblogs.com/mixls1234/p/3185950.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值