Class: WindowsLiveLogin

Inherits:
Object
  • Object
show all
Defined in:
lib/contacts/windowslivelogin.rb,
lib/contacts/windowslivelogin.rb,
lib/contacts/windowslivelogin.rb,
lib/contacts/windowslivelogin.rb,
lib/contacts/windowslivelogin.rb,
lib/contacts/windowslivelogin.rb

Overview

Helper methods.

Defined Under Namespace

Classes: ConsentToken, User

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(appid = nil, secret = nil, securityalgorithm = nil, force_delauth_nonprovisioned = nil, policyurl = nil, returnurl = nil) ⇒ WindowsLiveLogin

Initialize the WindowsLiveLogin module with the application ID, secret key, and security algorithm.

We recommend that you employ strong measures to protect the secret key. The secret key should never be exposed to the Web or other users.

Be aware that if you do not supply these settings at initialization time, you may need to set the corresponding properties manually.

For Delegated Authentication, you may optionally specify the privacy policy URL and return URL. If you do not specify these values here, the default values that you specified when you registered your application will be used.

The ‘force_delauth_nonprovisioned’ flag also indicates whether your application is registered for Delegated Authentication (that is, whether it uses an application ID and secret key). We recommend that your Delegated Authentication application always be registered for enhanced security and functionality.



76
77
78
79
80
81
82
83
84
85
# File 'lib/contacts/windowslivelogin.rb', line 76

def initialize(appid=nil, secret=nil, securityalgorithm=nil, 
               force_delauth_nonprovisioned=nil, 
               policyurl=nil, returnurl=nil)
  self.force_delauth_nonprovisioned = force_delauth_nonprovisioned
  self.appid = appid if appid
  self.secret = secret if secret
  self.securityalgorithm = securityalgorithm if securityalgorithm
  self.policyurl = policyurl if policyurl
  self.returnurl = returnurl if returnurl
end

Instance Attribute Details

#baseurlObject

Sets or gets the base URL to use for the Windows Live Login server. You should not have to change this property. Furthermore, we recommend that you use the Sign In control instead of the URL methods provided here.



288
289
290
# File 'lib/contacts/windowslivelogin.rb', line 288

def baseurl
  @baseurl
end

#consenturlObject

Sets or gets the Consent Base URL to use for the Windows Live Consent server. You should not have to use or change this property directly.



316
317
318
# File 'lib/contacts/windowslivelogin.rb', line 316

def consenturl
  @consenturl
end

#force_delauth_nonprovisionedObject

Sets a flag that indicates whether Delegated Authentication is non-provisioned (i.e. does not use an application ID or secret key).



232
233
234
# File 'lib/contacts/windowslivelogin.rb', line 232

def force_delauth_nonprovisioned
  @force_delauth_nonprovisioned
end

#oldsecretexpiryObject

Gets the old secret key expiry time.



212
213
214
# File 'lib/contacts/windowslivelogin.rb', line 212

def oldsecretexpiry
  @oldsecretexpiry
end

#secureurlObject

Sets or gets the secure (HTTPS) URL to use for the Windows Live Login server. You should not have to change this property.



302
303
304
# File 'lib/contacts/windowslivelogin.rb', line 302

def secureurl
  @secureurl
end

#securityalgorithmObject

Sets or gets the version of the security algorithm being used.



217
218
219
# File 'lib/contacts/windowslivelogin.rb', line 217

def securityalgorithm
  @securityalgorithm
end

Class Method Details

.initFromXml(settingsFile) ⇒ Object

Initialize the WindowsLiveLogin module from a settings file.

‘settingsFile’ specifies the location of the XML settings file that contains the application ID, secret key, and security algorithm. The file is of the following format:

<windowslivelogin>

<appid>APPID</appid>
<secret>SECRET</secret>
<securityalgorithm>wsignin1.0</securityalgorithm>

</windowslivelogin>

In a Delegated Authentication scenario, you may also specify ‘returnurl’ and ‘policyurl’ in the settings file, as shown in the Delegated Authentication samples.

We recommend that you store the WindowsLiveLogin settings file in an area on your server that cannot be accessed through the Internet. This file contains important confidential information.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/contacts/windowslivelogin.rb', line 108

def self.initFromXml(settingsFile)
  o = self.new
  settings = o.parseSettings(settingsFile)

  o.setDebug(settings['debug'] == 'true')
  o.force_delauth_nonprovisioned = 
    (settings['force_delauth_nonprovisioned'] == 'true')

  o.appid = settings['appid']
  o.secret = settings['secret']
  o.oldsecret = settings['oldsecret']
  o.oldsecretexpiry = settings['oldsecretexpiry']
  o.securityalgorithm = settings['securityalgorithm']
  o.policyurl = settings['policyurl']
  o.returnurl = settings['returnurl']
  o.baseurl = settings['baseurl']
  o.secureurl = settings['secureurl']
  o.consenturl = settings['consenturl']
  o
end

Instance Method Details

#appidObject

Returns the application ID.



147
148
149
150
151
152
# File 'lib/contacts/windowslivelogin.rb', line 147

def appid
  if (@appid.nil? or @appid.empty?)
    fatal("Error: appid: App ID was not set. Aborting.")
  end
  @appid
end

#appid=(appid) ⇒ Object

Sets the application ID. Use this method if you did not specify an application ID at initialization.



133
134
135
136
137
138
139
140
141
142
# File 'lib/contacts/windowslivelogin.rb', line 133

def appid=(appid)
  if (appid.nil? or appid.empty?)
    return if force_delauth_nonprovisioned
    fatal("Error: appid: Null application ID.") 
  end
  if (not appid =~ /^\w+$/)
    fatal("Error: appid: Application ID must be alpha-numeric: " + appid)
  end
  @appid = appid
end

#debug(error) ⇒ Object

Stub implementation for logging errors. By default, this function does nothing if the debug flag has not been set with setDebug. Otherwise, it tries to log the error message.



38
39
40
41
42
43
# File 'lib/contacts/windowslivelogin.rb', line 38

def debug(error)
  return unless @debug
  return if error.nil? or error.empty?
  warn("Windows Live ID Authentication SDK #{error}")
  nil 
end

#decodeAndValidateToken(token, cryptkey = @cryptkey, signkey = @signkey, internal_allow_recursion = true) ⇒ Object

Decodes and validates the token.



807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
# File 'lib/contacts/windowslivelogin.rb', line 807

def decodeAndValidateToken(token, cryptkey=@cryptkey, signkey=@signkey,
                           internal_allow_recursion=true)
  haveoldsecret = false
  if (oldsecretexpiry and (Time.now.to_i < oldsecretexpiry.to_i))
    haveoldsecret = true if (@oldcryptkey and @oldsignkey)
  end
  haveoldsecret = (haveoldsecret and internal_allow_recursion)

  stoken = decodeToken(token, cryptkey)
  stoken = validateToken(stoken, signkey) if stoken
  if (stoken.nil? and haveoldsecret)
    debug("Warning: Failed to validate token with current secret, attempting old secret.")
    stoken = decodeAndValidateToken(token, @oldcryptkey, @oldsignkey, false)
  end
  stoken
end

#decodeToken(token, cryptkey = @cryptkey) ⇒ Object

Decodes the given token string; returns undef on failure.

First, the string is URL-unescaped and base64 decoded. Second, the IV is extracted from the first 16 bytes of the string. Finally, the string is decrypted using the encryption key.



831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
# File 'lib/contacts/windowslivelogin.rb', line 831

def decodeToken(token, cryptkey=@cryptkey)
  if (cryptkey.nil? or cryptkey.empty?)
    fatal("Error: decodeToken: Secret key was not set. Aborting.")
  end
  token =  u64(token)
  if (token.nil? or (token.size <= 16) or !(token.size % 16).zero?)
    debug("Error: decodeToken: Attempted to decode invalid token.")
    return
  end
  iv = token[0..15]
  crypted = token[16..-1]
  begin
    aes128cbc = OpenSSL::Cipher::AES128.new("CBC")
    aes128cbc.decrypt
    aes128cbc.iv = iv
    aes128cbc.key = cryptkey
    decrypted = aes128cbc.update(crypted) + aes128cbc.final
  rescue Exception => e
    debug("Error: decodeToken: Decryption failed: #{token}, #{e}")
    return
  end
  decrypted
end

#derive(secret, prefix) ⇒ Object

Derives the key, given the secret key and prefix as described in the Web Authentication SDK documentation.



1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
# File 'lib/contacts/windowslivelogin.rb', line 1067

def derive(secret, prefix)
  begin
    fatal("Nil/empty secret.") if (secret.nil? or secret.empty?)
    key = prefix + secret
    key = OpenSSL::Digest::SHA256.digest(key)
    return key[0..15]
  rescue Exception => e
    debug("Error: derive: #{e}")
    return
  end
end

#e64(s) ⇒ Object

Base64-encodes and URL-escapes a string.



1120
1121
1122
1123
# File 'lib/contacts/windowslivelogin.rb', line 1120

def e64(s)
  return unless s
  CGI.escape Base64.encode64(s)
end

#fatal(error) ⇒ Object

Stub implementation for handling a fatal error.



48
49
50
51
# File 'lib/contacts/windowslivelogin.rb', line 48

def fatal(error)
  debug(error)
  raise(error)
end

#fetch(url) ⇒ Object

Fetches the contents given a URL.



1136
1137
1138
1139
1140
1141
# File 'lib/contacts/windowslivelogin.rb', line 1136

def fetch(url)
    url = URI.parse url
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = (url.scheme == "https")
    http.request_get url.request_uri
end

#getAppLoginUrl(siteid = nil, ip = nil, js = nil) ⇒ Object

Returns the URL that is required to retrieve the application security token.

By default, the application security token is generated for the Windows Live site; a specific Site ID can optionally be specified in ‘siteid’. The IP address can also optionally be included in ‘ip’.

If ‘js’ is nil, a JavaScript Output Notation (JSON) response is returned in the following format:

“token”:“<value>”

Otherwise, a JavaScript response is returned. It is assumed that WLIDResultCallback is a custom function implemented to handle the token value:

WLIDResultCallback(“<tokenvalue>”);



928
929
930
931
932
933
934
# File 'lib/contacts/windowslivelogin.rb', line 928

def getAppLoginUrl(siteid=nil, ip=nil, js=nil)
  url = secureurl + "wapplogin.srf?app=#{getAppVerifier(ip)}"
  url += "&alg=#{securityalgorithm}"
  url += "&id=#{siteid}" if siteid
  url += "&js=1" if js
  url
end

#getAppRetCodeObject

Returns a string that can be passed to the getTrustedParams function as the ‘retcode’ parameter. If this is specified as the ‘retcode’, the application will be used as return URL after it finishes trusted sign-in.



975
976
977
# File 'lib/contacts/windowslivelogin.rb', line 975

def getAppRetCode
  "appid=#{appid}"
end

#getAppSecurityToken(siteid = nil, ip = nil) ⇒ Object

Retrieves the application security token for application verification from the application sign-in URL.

By default, the application security token will be generated for the Windows Live site; a specific Site ID can optionally be specified in ‘siteid’. The IP address can also optionally be included in ‘ip’.

Implementation note: The application security token is downloaded from the application sign-in URL in JSON format:

“token”:“<value>”

Therefore we must extract <value> from the string and return it as seen here.



953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
# File 'lib/contacts/windowslivelogin.rb', line 953

def getAppSecurityToken(siteid=nil, ip=nil)
  url = getAppLoginUrl(siteid, ip)
  begin
    ret = fetch url
    ret.value # raises exception if fetch failed
    body = ret.body
    body.scan(/\{"token":"(.*)"\}/){|match|
      return match
    }
    debug("Error: getAppSecurityToken: Failed to extract token: #{body}")
  rescue Exception => e
    debug("Error: getAppSecurityToken: Failed to get token: #{e}")
  end    
  return
end

#getAppVerifier(ip = nil) ⇒ Object

Generates an application verifier token. An IP address can optionally be included in the token.



901
902
903
904
905
906
# File 'lib/contacts/windowslivelogin.rb', line 901

def getAppVerifier(ip=nil)
  token = "appid=#{appid}&ts=#{timestamp}"
  token += "&ip=#{ip}" if ip
  token += "&sig=#{e64(signToken(token))}"
  CGI.escape token
end

#getClearCookieResponseObject

Returns an appropriate content type and body response that the application handler can return to signify a successful sign-out from the application.

When a user signs out of Windows Live or a Windows Live application, a best-effort attempt is made at signing the user out from all other Windows Live applications the user might be signed in to. This is done by calling the handler page for each application with ‘action’ set to ‘clearcookie’ in the query string. The application handler is then responsible for clearing any cookies or data associated with the sign-in. After successfully signing the user out, the handler should return a GIF (any GIF) image as response to the ‘action=clearcookie’ query.



494
495
496
497
498
499
# File 'lib/contacts/windowslivelogin.rb', line 494

def getClearCookieResponse()
  type = "image/gif"
  content = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7"
  content = Base64.decode64(content)
  return type, content
end

#getConsentUrl(offers, context = nil, ru = nil, market = nil) ⇒ Object

Returns the consent URL to use for Delegated Authentication for the given comma-delimited list of offers.

If you specify it, ‘context’ will be returned as-is in the consent response for site-specific use.

The registered/configured return URL can also be overridden by specifying ‘ru’ here.

You can change the language in which the consent page is displayed by specifying a culture ID (For example, ‘fr-fr’ or ‘en-us’) in the ‘market’ parameter.



521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/contacts/windowslivelogin.rb', line 521

def getConsentUrl(offers, context=nil, ru=nil, market=nil)
  if (offers.nil? or offers.empty?)
    fatal("Error: getConsentUrl: Invalid offers list.")
  end
  url = consenturl + "Delegation.aspx?ps=#{CGI.escape(offers)}"
  url += "&appctx=#{CGI.escape(context)}" if context
  ru = returnurl if (ru.nil? or ru.empty?)
  url += "&ru=#{CGI.escape(ru)}" if ru
  pu = policyurl
  url += "&pl=#{CGI.escape(pu)}" if pu
  url += "&mkt=#{CGI.escape(market)}" if market
  url += "&app=#{getAppVerifier()}" unless force_delauth_nonprovisioned
  url
end

#getLoginUrl(context = nil, market = nil) ⇒ Object

Returns the sign-in URL to use for the Windows Live Login server. We recommend that you use the Sign In control instead.

If you specify it, ‘context’ will be returned as-is in the sign-in response for site-specific use.



338
339
340
341
342
343
344
# File 'lib/contacts/windowslivelogin.rb', line 338

def getLoginUrl(context=nil, market=nil)
  url = baseurl + "wlogin.srf?appid=#{appid}"
  url += "&alg=#{securityalgorithm}"
  url += "&appctx=#{CGI.escape(context)}" if context
  url += "&mkt=#{CGI.escape(market)}" if market
  url
end

#getLogoutUrl(market = nil) ⇒ Object

Returns the sign-out URL to use for the Windows Live Login server. We recommend that you use the Sign In control instead.



350
351
352
353
354
# File 'lib/contacts/windowslivelogin.rb', line 350

def getLogoutUrl(market=nil)
  url = baseurl + "logout.srf?appid=#{appid}"
  url += "&mkt=#{CGI.escape(market)}" if market
  url
end

#getManageConsentUrl(market = nil) ⇒ Object

Returns the URL for the consent-management user interface. You can change the language in which the consent page is displayed by specifying a culture ID (For example, ‘fr-fr’ or ‘en-us’) in the ‘market’ parameter.



563
564
565
566
567
# File 'lib/contacts/windowslivelogin.rb', line 563

def getManageConsentUrl(market=nil)
  url = consenturl + "ManageConsent.aspx"
  url += "?mkt=#{CGI.escape(market)}" if market
  url
end

#getRefreshConsentTokenUrl(offers, refreshtoken, ru) ⇒ Object

Returns the URL to use to download a new consent token, given the offers and refresh token. The registered/configured return URL can also be overridden by specifying ‘ru’ here.



542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/contacts/windowslivelogin.rb', line 542

def getRefreshConsentTokenUrl(offers, refreshtoken, ru)
  if (offers.nil? or offers.empty?)
    fatal("Error: getRefreshConsentTokenUrl: Invalid offers list.")
  end
  if (refreshtoken.nil? or refreshtoken.empty?)
    fatal("Error: getRefreshConsentTokenUrl: Invalid refresh token.")
  end
  url = consenturl + "RefreshToken.aspx?ps=#{CGI.escape(offers)}"    
  url += "&reft=#{refreshtoken}"
  ru = returnurl if (ru.nil? or ru.empty?)
  url += "&ru=#{CGI.escape(ru)}" if ru
  url += "&app=#{getAppVerifier()}" unless force_delauth_nonprovisioned
  url
end

#getTrustedLoginUrlObject

Returns the trusted sign-in URL to use for the Windows Live Login server.



1027
1028
1029
# File 'lib/contacts/windowslivelogin.rb', line 1027

def getTrustedLoginUrl
  secureurl + "wlogin.srf"
end

#getTrustedLogoutUrlObject

Returns the trusted sign-out URL to use for the Windows Live Login server.



1035
1036
1037
# File 'lib/contacts/windowslivelogin.rb', line 1035

def getTrustedLogoutUrl
  secureurl + "logout.srf?appid=#{appid}"
end

#getTrustedParams(user, retcode = nil) ⇒ Object

Returns a table of key-value pairs that must be posted to the sign-in URL for trusted sign-in. Use HTTP POST to do this. Be aware that the values in the table are neither URL nor HTML escaped and may have to be escaped if you are inserting them in code such as an HTML form.

The user to be trusted on the local site is passed in as string ‘user’.

Optionally, ‘retcode’ specifies the resource to which successful sign-in is redirected, such as Windows Live Mail, and is typically a string in the format ‘id=2000’. If you pass in the value from getAppRetCode instead, sign-in will be redirected to the application. Otherwise, an HTTP 200 response is returned.



995
996
997
998
999
1000
1001
1002
1003
1004
# File 'lib/contacts/windowslivelogin.rb', line 995

def getTrustedParams(user, retcode=nil)
  token = getTrustedToken(user)
  return unless token
  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>}
  params = {}
  params['wa'] = securityalgorithm
  params['wresult'] = token
  params['wctx'] = retcode if retcode
  params
end

#getTrustedToken(user) ⇒ Object

Returns the trusted sign-in token in the format that is needed by a control doing trusted sign-in.

The user to be trusted on the local site is passed in as string ‘user’.



1013
1014
1015
1016
1017
1018
1019
1020
1021
# File 'lib/contacts/windowslivelogin.rb', line 1013

def getTrustedToken(user)
  if user.nil? or user.empty?
    debug('Error: getTrustedToken: Null user specified.')
    return
  end
  token = "appid=#{appid}&uid=#{CGI.escape(user)}&ts=#{timestamp}"
  token += "&sig=#{e64(signToken(token))}"
  CGI.escape token
end

#oldsecret=(secret) ⇒ Object

Sets your old secret key.

Use this property to set your old secret key if you are in the process of transitioning to a new secret key. You may need this property because the Windows Live ID servers can take up to 24 hours to propagate a new secret key after you have updated your application settings.

If an old secret key is specified here and has not expired (as determined by the oldsecretexpiry setting), it will be used as a fallback if token decryption fails with the new secret key.



184
185
186
187
188
189
190
191
# File 'lib/contacts/windowslivelogin.rb', line 184

def oldsecret=(secret)
  return if (secret.nil? or secret.empty?)
  if (secret.size < 16)
    fatal("Error: oldsecret=: Secret must be at least 16 characters.")       
  end
  @oldsignkey = derive(secret, "SIGNATURE")
  @oldcryptkey = derive(secret, "ENCRYPTION")
end

#parse(input) ⇒ Object

Parses query string and return a table String=>String

If a table is passed in from CGI.params, we convert it from String=>[] to String=>String. I believe Rails uses symbols instead of strings in general, so we convert from symbols to strings here also.



1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
# File 'lib/contacts/windowslivelogin.rb', line 1088

def parse(input)
  if (input.nil? or input.empty?)
    debug("Error: parse: Nil/empty input.")
    return
  end

  pairs = {}
  if (input.class == String)
    input = input.split('&')
    input.each{|pair|
      k, v = pair.split('=')
      pairs[k] = v
    }
  else
    input.each{|k, v|
      v = v[0] if (v.class == Array)
      pairs[k.to_s] = v.to_s
    }
  end
  return pairs
end

#parseSettings(settingsFile) ⇒ Object

Function to parse the settings file.



1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
# File 'lib/contacts/windowslivelogin.rb', line 1048

def parseSettings(settingsFile)
  settings = {}
  begin
    file = File.new(settingsFile)
    doc = REXML::Document.new file
    root = doc.root
    root.each_element{|e|
      settings[e.name] = e.text
    }
  rescue Exception => e
    fatal("Error: parseSettings: Error while reading #{settingsFile}: #{e}")
  end
  return settings
end

#policyurlObject

Gets the privacy policy URL for your site.



249
250
251
252
253
254
255
# File 'lib/contacts/windowslivelogin.rb', line 249

def policyurl
  if (@policyurl.nil? or @policyurl.empty?)
    debug("Warning: In the initial release of Del Auth, a Policy URL must be configured in the SDK for both provisioned and non-provisioned scenarios.")
    raise("Error: policyurl: Policy URL must be set in a Del Auth non-provisioned scenario. Aborting.") if force_delauth_nonprovisioned
  end
  @policyurl
end

#policyurl=(policyurl) ⇒ Object

Sets the privacy policy URL, to which the Windows Live ID consent service redirects users to view the privacy policy of your Web site for Delegated Authentication.



239
240
241
242
243
244
# File 'lib/contacts/windowslivelogin.rb', line 239

def policyurl=(policyurl)
  if ((policyurl.nil? or policyurl.empty?) and force_delauth_nonprovisioned)
    fatal("Error: policyurl=: Invalid policy URL specified.")
  end
  @policyurl = policyurl
end

#processConsent(query) ⇒ Object

Processes the POST response from the Delegated Authentication service after a user has granted consent. The processConsent function extracts the consent token string and returns the result of invoking the processConsentToken method.



702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
# File 'lib/contacts/windowslivelogin.rb', line 702

def processConsent(query)
  query = parse query
  unless query
    debug("Error: processConsent: Failed to parse query.")
    return
  end
  action = query['action']
  unless action == 'delauth'
    debug("Warning: processConsent: query action ignored: #{action}.")
    return
  end
  responsecode = query['ResponseCode']
  unless responsecode == 'RequestApproved'
    debug("Error: processConsent: Consent was not successfully granted: #{responsecode}")
    return
  end
  token = query['ConsentToken']
  context = CGI.unescape(query['appctx']) if query['appctx']
  processConsentToken(token, context)
end

#processConsentToken(token, context = nil) ⇒ Object

Processes the consent token string that is returned in the POST response by the Delegated Authentication service after a user has granted consent.



728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
# File 'lib/contacts/windowslivelogin.rb', line 728

def processConsentToken(token, context=nil)
  if token.nil? or token.empty?
    debug("Error: processConsentToken: Null token.")
    return
  end
  decodedtoken = token
  parsedtoken = parse(CGI.unescape(decodedtoken))
  unless parsedtoken
    debug("Error: processConsentToken: Failed to parse token: #{token}")
    return
  end
  eact = parsedtoken['eact']
  if eact
    decodedtoken = decodeAndValidateToken eact
    unless decodedtoken
      debug("Error: processConsentToken: Failed to decode/validate token: #{token}")
      return
    end
    parsedtoken = parse(decodedtoken)
    decodedtoken = CGI.escape(decodedtoken)
  end
  begin
    consenttoken = ConsentToken.new(self, 
                                    parsedtoken['delt'],
                                    parsedtoken['reft'],
                                    parsedtoken['skey'],
                                    parsedtoken['exp'],
                                    parsedtoken['offer'],
                                    parsedtoken['lid'],
                                    context, decodedtoken, token)
    return consenttoken
  rescue Exception => e
    debug("Error: processConsentToken: Contents of token considered invalid: #{e}")
    return
  end
end

#processLogin(query) ⇒ Object

Processes the sign-in response from the Windows Live sign-in server.

‘query’ contains the preprocessed POST table, such as that returned by CGI.params or Rails. (The unprocessed POST string could also be used here but we do not recommend it).

This method returns a User object on successful sign-in; otherwise it returns nil.



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/contacts/windowslivelogin.rb', line 432

def processLogin(query)
  query = parse query
  unless query
    debug("Error: processLogin: Failed to parse query.")
    return
  end
  action = query['action']
  unless action == 'login'
    debug("Warning: processLogin: query action ignored: #{action}.")
    return
  end
  token = query['stoken']
  context = CGI.unescape(query['appctx']) if query['appctx']
  processToken(token, context)
end

#processToken(token, context = nil) ⇒ Object

Decodes and validates a Web Authentication token. Returns a User object on success. If a context is passed in, it will be returned as the context field in the User object.



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/contacts/windowslivelogin.rb', line 453

def processToken(token, context=nil)
  if token.nil? or token.empty?
    debug("Error: processToken: Null/empty token.")
    return
  end
  stoken = decodeAndValidateToken token
  stoken = parse stoken
  unless stoken
    debug("Error: processToken: Failed to decode/validate token: #{token}")
    return
  end
  sappid = stoken['appid']
  unless sappid == appid
    debug("Error: processToken: Application ID in token did not match ours: #{sappid}, #{appid}")
    return
  end
  begin
    user = User.new(stoken['ts'], stoken['uid'], stoken['flags'], 
                    context, token)
    return user
  rescue Exception => e
    debug("Error: processToken: Contents of token considered invalid: #{e}")
    return
  end
end

#refreshConsentToken(consenttoken, ru = nil) ⇒ Object

Attempts to obtain a new, refreshed token and return it. The original token is not modified.



769
770
771
772
773
774
775
# File 'lib/contacts/windowslivelogin.rb', line 769

def refreshConsentToken(consenttoken, ru=nil)
  if consenttoken.nil?
    debug("Error: refreshConsentToken: Null consent token.")
    return
  end
  refreshConsentToken2(consenttoken.offers_string, consenttoken.refreshtoken, ru)
end

#refreshConsentToken2(offers_string, refreshtoken, ru = nil) ⇒ Object

Helper function to obtain a new, refreshed token and return it. The original token is not modified.



781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
# File 'lib/contacts/windowslivelogin.rb', line 781

def refreshConsentToken2(offers_string, refreshtoken, ru=nil)
  url = nil
  begin
    url = getRefreshConsentTokenUrl(offers_string, refreshtoken, ru)
    ret = fetch url
    ret.value # raises exception if fetch failed
    body = ret.body
    body.scan(/\{"ConsentToken":"(.*)"\}/){|match|
    return processConsentToken("#{match}")
    }
    debug("Error: refreshConsentToken2: Failed to extract token: #{body}")
  rescue Exception => e
    debug("Error: Failed to refresh consent token: #{e}")
  end
  return
end

#returnurlObject

Returns the return URL of your site.



275
276
277
278
279
280
# File 'lib/contacts/windowslivelogin.rb', line 275

def returnurl
  if ((@returnurl.nil? or @returnurl.empty?) and force_delauth_nonprovisioned)
    fatal("Error: returnurl: Return URL must be set in a Del Auth non-provisioned scenario. Aborting.")
  end
  @returnurl
end

#returnurl=(returnurl) ⇒ Object

Sets the return URL–the URL on your site to which the consent service redirects users (along with the action, consent token, and application context) after they have successfully provided consent information for Delegated Authentication. This value will override the return URL specified during registration.



264
265
266
267
268
269
# File 'lib/contacts/windowslivelogin.rb', line 264

def returnurl=(returnurl)
  if ((returnurl.nil? or returnurl.empty?) and force_delauth_nonprovisioned)
    fatal("Error: returnurl=: Invalid return URL specified.")
  end
  @returnurl = returnurl
end

#secret=(secret) ⇒ Object

Sets your secret key. Use this method if you did not specify a secret key at initialization.



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/contacts/windowslivelogin.rb', line 158

def secret=(secret)
  if (secret.nil? or secret.empty?)
    return if force_delauth_nonprovisioned
    fatal("Error: secret=: Secret must be non-null.") 
  end
  if (secret.size < 16)
    fatal("Error: secret=: Secret must be at least 16 characters.")       
  end
  @signkey = derive(secret, "SIGNATURE")
  @cryptkey = derive(secret, "ENCRYPTION")
end

#setDebug(flag) ⇒ Object

Stub implementation for logging errors. If you want to enable debugging output using the default mechanism, specify true. By default, debug information will be printed to the standard error output and should be visible in the web server logs.



29
30
31
# File 'lib/contacts/windowslivelogin.rb', line 29

def setDebug(flag) 
  @debug = flag
end

#signToken(token, signkey = @signkey) ⇒ Object

Creates a signature for the given string by using the signature key.



859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/contacts/windowslivelogin.rb', line 859

def signToken(token, signkey=@signkey)
  if (signkey.nil? or signkey.empty?)
    fatal("Error: signToken: Secret key was not set. Aborting.")
  end
  begin
    digest = OpenSSL::Digest::SHA256.new
    return OpenSSL::HMAC.digest(digest, signkey, token)
  rescue Exception => e
    debug("Error: signToken: Signing failed: #{token}, #{e}")
    return
  end
end

#timestampObject

Generates a time stamp suitable for the application verifier token.



1113
1114
1115
# File 'lib/contacts/windowslivelogin.rb', line 1113

def timestamp
  Time.now.to_i.to_s
end

#u64(s) ⇒ Object

URL-unescapes and Base64-decodes a string.



1128
1129
1130
1131
# File 'lib/contacts/windowslivelogin.rb', line 1128

def u64(s)
  return unless s
  Base64.decode64 CGI.unescape(s)
end

#validateToken(token, signkey = @signkey) ⇒ Object

Extracts the signature from the token and validates it.



875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
# File 'lib/contacts/windowslivelogin.rb', line 875

def validateToken(token, signkey=@signkey)
  if (token.nil? or token.empty?)
    debug("Error: validateToken: Null token.")
    return
  end
  body, sig = token.split("&sig=")
  if (body.nil? or sig.nil?)
    debug("Error: validateToken: Invalid token: #{token}")
    return
  end
  sig = u64(sig)
  return token if (sig == signToken(body, signkey))
  debug("Error: validateToken: Signature did not match.")
  return
end