总结下之前项目中用到的,如何调用公共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":"<value>"} 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":"<value>"} 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":"<value>"} 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":"<value>"} 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("<tokenvalue>"); 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":"<value>"}, so we need to extract 1994 /// <value> 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 }
其中用的核心类库,是微软提供的开源类库,可以直接用
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 }
登出的方法
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");
核心获取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;
这里返回的是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 }
最后,绑定到页面显示,齐活