Skip to content
Browse files

Rewrite of the authentication request part

  • Loading branch information...
1 parent 480faf1 commit 231e73ae19de1a6974debcdbc24a5f589fba8db6 @fduraffourg fduraffourg committed
Showing with 391 additions and 396 deletions.
  1. +27 −0 LICENSE
  2. +4 −5 Makefile
  3. 0 README
  4. +8 −7 README.markdown
  5. +104 −0 authrequest.go
  6. +54 −0 authrequest_test.go
  7. +0 −127 http.go
  8. +0 −204 openid.go
  9. +15 −10 xrds.go
  10. +32 −0 xrds_test.go
  11. +86 −43 yadis.go
  12. +61 −0 yadis_test.go
View
27 LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2010 Florian Duraffourg. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Florian Duraffourg. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
9 Makefile
@@ -1,11 +1,10 @@
-include $(GOROOT)/src/Make.$(GOARCH)
+include $(GOROOT)/src/Make.inc
TARG=openid
GOFILES=\
- openid.go\
- yadis.go \
- xrds.go \
- http.go
+ authrequest.go\
+ xrds.go\
+ yadis.go
include $(GOROOT)/src/Make.pkg
View
0 README
No changes.
View
15 README.markdown
@@ -4,7 +4,7 @@ Go-OpenID
About
-----
-Go-OpenID is an implementation of OpenID in Golang.
+Go-OpenID is an implementation of OpenID authentication protocol in Golang.
For now, the implementation does not manage XRI identifier, and can only check authentication with a direct request.
@@ -21,13 +21,14 @@ Install
Usage
-----
- var o = new(openid.OpenID)
- o.Identifier = "https://www.google.com/accounts/o8/id"
- o.Realm = "http://example.com"
- o.ReturnTo = "/loginCheck"
- url := o.GetUrl()
+ url := openid.GetRedirectURL("Identifier", "http://www.realm.com", "/loginCheck")
+
+Now you have to redirect the user to the url returned. The OP will then forward the user back to you, after authenticating him.
+
+** What follows has been removed from the code because it was not compliant with newer go code.**
+** Please wait a bit or use git history **
-Now you have to redirect the user to the url returned. The OP will then forward the user back to you. To check the identity, do that:
+To check the identity, do that:
var o = new(openid.OpenID)
o.ParseRPUrl(URL)
View
104 authrequest.go
@@ -0,0 +1,104 @@
+// Copyright 2010 Florian Duraffourg. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openid
+
+import (
+ "strings"
+ "io"
+ "os"
+ "http"
+)
+
+const (
+ _ = iota
+ IdentifierXRI
+ IdentifierURL
+)
+
+
+func GetRedirectURL (Identifier string, realm string, returnto string) (string, os.Error) {
+ var err os.Error
+ var Id, IdType = NormalizeIdentifier(Identifier)
+
+ // If the identifier is an XRI, [XRI_Resolution_2.0] will yield an XRDS document that contains the necessary information. It should also be noted that Relying Parties can take advantage of XRI Proxy Resolvers, such as the one provided by XDI.org at http://www.xri.net. This will remove the need for the RPs to perform XRI Resolution locally.
+ if IdType == IdentifierXRI {
+ // Not implemented yet
+ return "", os.ErrorString("XRI identifier not implemented yed")
+ }
+
+ // If it is a URL, the Yadis protocol [Yadis] SHALL be first attempted. If it succeeds, the result is again an XRDS document.
+ if IdType == IdentifierURL {
+ var reader io.Reader
+ reader, err = Yadis(Id)
+ if err != nil {
+ return "", err
+ }
+
+ var endpoint, claimedid = ParseXRDS(reader)
+ if len(endpoint) == 0 {
+ return "", os.ErrorString("Unable to parse the XRDS document")
+ }
+
+ // At this point we have the endpoint and eventually a claimed id
+ // Create the authentication request
+ return CreateAuthenticationRequest(endpoint, claimedid, realm, returnto), nil
+ }
+
+ // If the Yadis protocol fails and no valid XRDS document is retrieved, or no Service Elements are found in the XRDS document, the URL is retrieved and HTML-Based discovery SHALL be attempted.
+
+
+ return "url", nil
+}
+
+func NormalizeIdentifier(Id string) (Identifier string, IdentifierType int) {
+ Identifier = Id
+ //1. If the user's input starts with the "xri://" prefix, it MUST be stripped off, so that XRIs are used in the canonical form.
+ if strings.HasPrefix(Identifier, "xri://") {
+ Identifier = Identifier[6:]
+ }
+
+ // 2. If the first character of the resulting string is an XRI Global Context Symbol ("=", "@", "+", "$", "!") or "(", as defined in Section 2.2.1 of [XRI_Syntax_2.0] (Reed, D. and D. McAlpin, “Extensible Resource Identifier (XRI) Syntax V2.0,” .), then the input SHOULD be treated as an XRI.
+ var firstChar = Identifier[0]
+ if firstChar == '=' || firstChar == '@' || firstChar == '+' || firstChar == '$' || firstChar == '!' {
+ IdentifierType = IdentifierXRI
+ return
+ }
+
+ // 3. Otherwise, the input SHOULD be treated as an http URL; if it does not include a "http" or "https" scheme, the Identifier MUST be prefixed with the string "http://". If the URL contains a fragment part, it MUST be stripped off together with the fragment delimiter character "#". See Section 11.5.2 (HTTP and HTTPS URL Identifiers) for more information.
+ IdentifierType = IdentifierURL
+ if ! strings.HasPrefix(Identifier, "http://") && ! strings.HasPrefix(Identifier, "https://") {
+ Identifier = "http://" + Identifier
+ }
+
+ // 4. URL Identifiers MUST then be further normalized by both following redirects when retrieving their content and finally applying the rules in Section 6 of [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) to the final destination URL. This final URL MUST be noted by the Relying Party as the Claimed Identifier and be used when requesting authentication (Requesting Authentication).
+
+ return
+}
+
+func CreateAuthenticationRequest(OPEndPoint, ClaimedID, Realm, ReturnTo string) string {
+ var p = make(map[string] string)
+
+ p["openid.ns"] = "http://specs.openid.net/auth/2.0"
+ p["openid.mode"] = "checkid_setup"
+
+ if len(ClaimedID) == 0 {
+ p["openid.claimed_id"] = "http://specs.openid.net/auth/2.0/identifier_select"
+ p["openid.identity"] = "http://specs.openid.net/auth/2.0/identifier_select"
+ } else {
+ p["openid.claimed_id"] = ClaimedID
+ p["openid.identity"] = ClaimedID
+ }
+
+ p["openid.return_to"] = Realm + ReturnTo
+ p["openid.realm"] = Realm
+
+ var url string
+ url = OPEndPoint + "?"
+
+ for k,v := range(p) {
+ url += http.URLEscape(k) + "=" + http.URLEscape(v) + "&"
+ }
+ return url
+}
View
54 authrequest_test.go
@@ -0,0 +1,54 @@
+// Copyright 2010 Florian Duraffourg. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openid
+
+import (
+ "testing"
+ "bytes"
+)
+
+// NormalizeIdentifier Test
+
+type NormalizeIdentifierTest struct {
+ in, out string
+ t int
+}
+
+var NormalizeIdentifierTests = []NormalizeIdentifierTest {
+ //NormalizeIdentifierTest{"example.com", "http://example.com/", IdentifierURL},
+ //NormalizeIdentifierTest{"http://example.com", "http://example.com/", IdentifierURL},
+ NormalizeIdentifierTest{"https://example.com/", "https://example.com/", IdentifierURL},
+ NormalizeIdentifierTest{"http://example.com/user", "http://example.com/user", IdentifierURL},
+ NormalizeIdentifierTest{"http://example.com/user/", "http://example.com/user/", IdentifierURL},
+ NormalizeIdentifierTest{"http://example.com/", "http://example.com/", IdentifierURL},
+ NormalizeIdentifierTest{"=example", "=example", IdentifierXRI},
+ NormalizeIdentifierTest{"xri://=example", "=example", IdentifierXRI},
+}
+
+func TestNormalizeIdentifier(testing *testing.T) {
+ for _, nit := range NormalizeIdentifierTests {
+ v, t := NormalizeIdentifier(nit.in)
+ if ! bytes.Equal([]byte(v), []byte(nit.out)) || t != nit.t {
+ testing.Errorf("NormalizeIdentifier(%s) = (%s, %d) want (%s, %d).", nit.in, v, t, nit.out, nit.t)
+ }
+ }
+}
+
+// GetRedirectURL Test
+
+var Identifiers = []string {
+ "https://www.google.com/accounts/o8/id",
+ "yahoo.com",
+}
+
+// Just check that there is no errors returned by GetRedirectURL
+func TestGetRedirectURL ( t *testing.T) {
+ for _, url := range Identifiers {
+ _,err := GetRedirectURL(url, "http://example.com", "/loginCheck")
+ if err != nil {
+ t.Errorf("GetRedirectURL() returned the error: %s", err.String())
+ }
+ }
+}
View
127 http.go
@@ -1,127 +0,0 @@
-package openid
-
-import (
- "http"
- "os"
- "io"
- "net"
- "encoding/base64"
- "strings"
- "bufio"
- "crypto/tls"
-)
-
-
-
-// get Taken from the golang source modifed to allow headers to be passed and no redirection allowed
-func get(url string, headers map[string]string) (r *http.Response, err os.Error) {
-
- var req http.Request
- if err != nil { return }
- req.Header = headers
- req.URL, err = http.ParseURL(url)
-
- r, err = send(&req)
- if err != nil { return }
- return
-}
-
-// post taken from Golang modified to allow Headers to be pased
-func post(url string, headers map[string]string, body io.Reader) (r *http.Response, err os.Error) {
- var req http.Request
- req.Method = "POST"
- req.ProtoMajor = 1
- req.ProtoMinor = 1
- req.Close = true
- req.Body = nopCloser{body}
- req.Header = headers
- req.TransferEncoding = []string{"chunked"}
-
- req.URL, err = http.ParseURL(url)
- if err != nil {
- return nil, err
- }
-
- return send(&req)
-}
-
-// Copyright (c) 2009 The Go Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-
-// From the http package - modified to allow Headers to be sent to the Post method
-type nopCloser struct {
- io.Reader
-}
-
-func (nopCloser) Close() os.Error { return nil }
-
-type readClose struct {
- io.Reader
- io.Closer
-}
-
-func send(req *http.Request) (resp *http.Response, err os.Error) {
- if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
- return nil, nil
- }
-
- addr := req.URL.Host
- if !hasPort(addr) {
- addr += ":" + req.URL.Scheme
- }
- info := req.URL.Userinfo
- if len(info) > 0 {
- enc := base64.URLEncoding
- encoded := make([]byte, enc.EncodedLen(len(info)))
- enc.Encode(encoded, []byte(info))
- if req.Header == nil {
- req.Header = make(map[string]string)
- }
- req.Header["Authorization"] = "Basic " + string(encoded)
- }
-
- var conn io.ReadWriteCloser
- if req.URL.Scheme == "http" {
- conn, err = net.Dial("tcp", "", addr)
- } else { // https
- conn, err = tls.Dial("tcp", "", addr)
- }
- if err != nil {
- return nil, err
- }
-
- err = req.Write(conn)
- if err != nil {
- conn.Close()
- return nil, err
- }
-
- reader := bufio.NewReader(conn)
- resp, err = http.ReadResponse(reader, req.Method)
- if err != nil {
- conn.Close()
- return nil, err
- }
-
- resp.Body = readClose{resp.Body, conn}
-
- return
-}
-
-
-func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
-
View
204 openid.go
@@ -1,204 +0,0 @@
-package openid
-
-import (
- "fmt"
- "io"
- "regexp"
- "os"
- "bytes"
- "http"
- "strings"
- )
-
-const (
- _ = iota
- IdentifierXRI
- IdentifierURL
-)
-
-type OpenID struct {
- Identifier string // Discovery
- IdentifierType int // Discovery
- OPEndPoint string // Discovery
- ClaimedIdentifier string // Discovery
- OPLocalIdentifier string // Discovery
- Params map[string] string
- RPUrl string
- Hostname string
- Request string
- Realm string
- ReturnTo string
-}
-
-func (o *OpenID) normalization() {
- //1. If the user's input starts with the "xri://" prefix, it MUST be stripped off, so that XRIs are used in the canonical form.
- if strings.HasPrefix(o.Identifier, "xri://") {
- o.Identifier = o.Identifier[6:]
- }
-
- // 2. If the first character of the resulting string is an XRI Global Context Symbol ("=", "@", "+", "$", "!") or "(", as defined in Section 2.2.1 of [XRI_Syntax_2.0] (Reed, D. and D. McAlpin, “Extensible Resource Identifier (XRI) Syntax V2.0,” .), then the input SHOULD be treated as an XRI.
- if o.Identifier[0] == '=' || o.Identifier[0] == '@' || o.Identifier[0] == '+' || o.Identifier[0] == '$' || o.Identifier[0] == '!' {
- o.IdentifierType = IdentifierXRI
- fmt.Printf("It is an XRI\n")
- return
- }
-
- // 3. Otherwise, the input SHOULD be treated as an http URL; if it does not include a "http" or "https" scheme, the Identifier MUST be prefixed with the string "http://". If the URL contains a fragment part, it MUST be stripped off together with the fragment delimiter character "#". See Section 11.5.2 (HTTP and HTTPS URL Identifiers) for more information.
- o.IdentifierType = IdentifierURL
- if ! strings.HasPrefix(o.Identifier, "http://") && ! strings.HasPrefix(o.Identifier, "https://") {
- o.Identifier = "http://" + o.Identifier
- }
-
- // 4. URL Identifiers MUST then be further normalized by both following redirects when retrieving their content and finally applying the rules in Section 6 of [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) to the final destination URL. This final URL MUST be noted by the Relying Party as the Claimed Identifier and be used when requesting authentication (Requesting Authentication).
-}
-
-func (o *OpenID) discovery() os.Error {
- //1. If the identifier is an XRI, [XRI_Resolution_2.0] (Wachob, G., Reed, D., Chasen, L., Tan, W., and S. Churchill, “Extensible Resource Identifier (XRI) Resolution V2.0 - Committee Draft 02,” .) will yield an XRDS document that contains the necessary information. It should also be noted that Relying Parties can take advantage of XRI Proxy Resolvers, such as the one provided by XDI.org at http://www.xri.net. This will remove the need for the RPs to perform XRI Resolution locally.
- if o.IdentifierType == IdentifierXRI {
- fmt.Printf("XRI Discovery not implemented yet\n")
- }
-
- //2. If it is a URL, the Yadis protocol (Miller, J., “Yadis Specification 1.0,” .) [Yadis] SHALL be first attempted. If it succeeds, the result is again an XRDS document.
- if o.IdentifierType == IdentifierURL {
- r,err := Yadis(o.Identifier)
- if err != nil {
- return err
- }
- o.ParseXRDS(r)
- }
-
- //3. If the Yadis protocol fails and no valid XRDS document is retrieved, or no Service Elements (OpenID Service Elements) are found in the XRDS document, the URL is retrieved and HTML-Based discovery (HTML-Based Discovery) SHALL be attempted.
- // Not Yet implemented
-
-
-
- // If the end user entered an OP Identifier, there is no Claimed Identifier. For the purposes of making OpenID Authentication requests, the value "http://specs.openid.net/auth/2.0/identifier_select" MUST be used as both the Claimed Identifier and the OP-Local Identifier when an OP Identifier is entered.
- if o.ClaimedIdentifier == "" {
- fmt.Printf("Set identifier_select\n")
- o.OPLocalIdentifier = "http://specs.openid.net/auth/2.0/identifier_select"
- o.ClaimedIdentifier = "http://specs.openid.net/auth/2.0/identifier_select"
- }
-
- return nil
-}
-
-func mapToUrlEnc (params map[string] string) string {
- url := ""
- for k,v := range (params) {
- url = fmt.Sprintf("%s&%s=%s",url,k,v)
- }
- return url[1:]
-}
-
-func urlEncToMap (url string) map[string] string {
- // We don't know how elements are in the URL so we create a list first and push elements on it
- pmap := make(map[string] string)
- //url,_ = http.URLUnescape(url)
- var start, end, eq, length int
- length = len(url)
- start = 0
- for start < length && url[start] != '?' { start ++ }
- end = start
- for end < length {
- start = end + 1
- eq = start
- for eq < length && url[eq] != '=' { eq++ }
- end = eq + 1
- for end < length && url[end] != '&' { end++ }
-
- fmt.Printf("Trouve: %s : %s\n", url[start:eq], url[eq+1:end])
- pmap[url[start:eq]] = url[eq+1:end]
- }
- return pmap
-}
-
-func (o *OpenID) GetUrl() (string, os.Error) {
- o.normalization()
- err := o.discovery()
- if err != nil { return "", err }
-
-
- params := map[string] string {
- "openid.ns": "http://specs.openid.net/auth/2.0",
- "openid.mode" : "checkid_setup",
- }
- if o.Realm != "" {
- params["openid.realm"] = o.Realm
- }
- if o.Realm != "" && o.ReturnTo != "" {
- params["openid.return_to"] = o.Realm + o.ReturnTo
- }
- if o.ClaimedIdentifier != "" {
- params["openid.claimed_id"] = o.ClaimedIdentifier
- if o.OPLocalIdentifier != "" {
- params["openid.identity"] = o.OPLocalIdentifier
- } else {
- params["openid.identity"] = o.ClaimedIdentifier
- }
- }
- return o.OPEndPoint + "?" + mapToUrlEnc(params), nil
-}
-
-func (o *OpenID) Verify() (grant bool, err os.Error) {
- grant = false
- err = nil
-
-
- // The value of "openid.return_to" matches the URL of the current request
- // if ! MExists(o.Params, "openid.return_to") {
- // err = os.ErrorString("The value of 'openid.return_to' is not defined")
- // return
- // }
- // if (fmt.Sprintf("%s%s", o.Hostname, o.Request) != o.Params["openid.return_to"]) {
- // err = os.ErrorString("The value of 'openid.return_to' does not match the URL of the current request")
- // return
- // }
-
- // Discovered information matches the information in the assertion
-
- // An assertion has not yet been accepted from this OP with the same value for "openid.response_nonce"
-
- // The signature on the assertion is valid and all fields that are required to be signed are signed
- grant, err = o.VerifyDirect()
-
- return
-}
-
-func (o *OpenID) ParseRPUrl(url string) {
- o.Params = urlEncToMap(url)
-}
-
-func (o *OpenID) VerifyDirect() (grant bool, err os.Error) {
- grant = false
- err = nil
-
- o.Params["openid.mode"] = "check_authentication"
-
- headers := map[string] string {
- "Content-Type" : "application/x-www-form-urlencoded",
- }
- url,_ := http.URLUnescape(o.Params["openid.op_endpoint"])
- fmt.Printf("Verification: %s\nParams: %s\n",url, mapToUrlEnc(o.Params))
- r,error := post(url,
- headers,
- bytes.NewBuffer([]byte(mapToUrlEnc(o.Params))))
- if error != nil {
- fmt.Printf("erreur: %s\n", error.String())
- err = error
- return
- }
- fmt.Printf("Post done\n")
- if (r != nil) {
- buffer := make([]byte, 1024)
- fmt.Printf("Buffer created\n")
- io.ReadFull(r.Body, buffer)
- fmt.Printf("Body extracted: %s\n", buffer)
- grant, err = regexp.Match("is_valid:true", buffer)
- fmt.Printf("Response: %v\n", grant)
- }else {
- err = os.ErrorString("No response from POST verification")
- return
- }
-
- return
-}
View
25 xrds.go
@@ -1,8 +1,11 @@
+// Copyright 2010 Florian Duraffourg. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
package openid
import (
"xml"
- "fmt"
"io"
"strings"
)
@@ -22,28 +25,30 @@ type XRDS struct {
XRD XRD
}
-func (o *OpenID) ParseXRDS(r io.Reader){
+// Parse a XRDS document provided through a io.Reader
+// Return the OP EndPoint and, if found, the Claimed Identifier
+func ParseXRDS(r io.Reader) (string, string) {
XRDS := new(XRDS)
err := xml.Unmarshal(r, XRDS)
if err != nil {
- fmt.Printf(err.String())
- return
+ //fmt.Printf(err.String())
+ return "", ""
}
XRDSI := XRDS.XRD.Service
XRDSI.URI = strings.TrimSpace(XRDSI.URI)
XRDSI.LocalID = strings.TrimSpace(XRDSI.LocalID)
- fmt.Printf("%v\n", XRDSI)
+ //fmt.Printf("%v\n", XRDSI)
if StringTableContains(XRDSI.Type,"http://specs.openid.net/auth/2.0/server") {
- o.OPEndPoint = XRDSI.URI
- fmt.Printf("OP Identifier Element found\n")
+ //fmt.Printf("OP Identifier Element found\n")
+ return XRDSI.URI, ""
} else if StringTableContains(XRDSI.Type, "http://specs.openid.net/auth/2.0/signon") {
- fmt.Printf("Claimed Identifier Element found\n")
- o.OPEndPoint = XRDSI.URI
- o.ClaimedIdentifier = XRDSI.LocalID
+ //fmt.Printf("Claimed Identifier Element found\n")
+ return XRDSI.URI, XRDSI.LocalID
}
+ return "", ""
}
View
32 xrds_test.go
@@ -0,0 +1,32 @@
+// Copyright 2010 Florian Duraffourg. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openid
+
+import (
+ "testing"
+ "bytes"
+)
+
+// ParseXRDS Test
+
+type ParseXRDSTest struct {
+ in []byte
+ OPEndPoint string
+ ClaimedId string
+}
+
+var ParseXRDSTests = []ParseXRDSTest {
+ ParseXRDSTest{[]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><xrds:XRDS xmlns:xrds=\"xri://$xrds\" xmlns=\"xri://$xrd*($v*2.0)\"><XRD><Service xmlns=\"xri://$xrd*($v*2.0)\">\n<Type>http://specs.openid.net/auth/2.0/signon</Type>\n <URI>https://www.exampleprovider.com/endpoint/</URI>\n <LocalID>https://exampleuser.exampleprovider.com/</LocalID>\n </Service></XRD></xrds:XRDS>"), "https://www.exampleprovider.com/endpoint/", "https://exampleuser.exampleprovider.com/"},
+ ParseXRDSTest{[]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\" xmlns=\"xri://$xrd*($v*2.0)\">\n<XRD>\n <Service>\n <Type>http://specs.openid.net/auth/2.0/server</Type>\n <Type>http://openid.net/srv/ax/1.0</Type>\n <Type>http://openid.net/sreg/1.0</Type>\n <Type>http://openid.net/extensions/sreg/1.1</Type>\n <URI priority=\"20\">http://openid.orange.fr/server/</URI>\n </Service>\n</XRD>\n</xrds:XRDS>"), "http://openid.orange.fr/server/", ""},
+}
+
+func TestParseXRDS(t *testing.T) {
+ for _, xrds := range ParseXRDSTests {
+ var opep, ci = ParseXRDS(bytes.NewBuffer(xrds.in))
+ if !bytes.Equal([]byte(opep), []byte(xrds.OPEndPoint)) || !bytes.Equal([]byte(ci), []byte(xrds.ClaimedId)) {
+ t.Errorf("ParseXRDS(%s) = (%s, %s) want (%s, %s).", xrds.in, opep, ci, xrds.OPEndPoint, xrds.ClaimedId)
+ }
+ }
+}
View
129 yadis.go
@@ -1,14 +1,97 @@
+// Copyright 2010 Florian Duraffourg. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
package openid
import (
- "io"
- "fmt"
"os"
+ "http"
"xml"
+ "fmt"
+ "io"
+ "bytes"
"strings"
)
-func searchHTMLMeta(r io.Reader) (string, os.Error) {
+
+func Yadis(ID string) (io.Reader, os.Error) {
+ r, err := YadisRequest(ID, "GET")
+ if (err != nil || r == nil) {
+ return nil, err
+ }
+
+ var contentType = r.Header.Get("Content-Type")
+
+ // If it is an XRDS document, return the Reader
+ if strings.HasPrefix(contentType, "application/xrds+xml") {
+ return r.Body, nil
+ }
+
+ // If it is an HTML doc search for meta tags
+ if bytes.Equal([]byte(contentType), []byte("text/html")) {
+ url, err := searchHTMLMetaXRDS(r.Body)
+ if err != nil {
+ return nil, err
+ }
+ return Yadis(url)
+ }
+
+ // If the response contain an X-XRDS-Location header
+ var xrds_location = r.Header.Get("X-Xrds-Location")
+ if len(xrds_location) > 0 {
+ return Yadis(xrds_location)
+ }
+
+ // If nothing is found try to parse it as a XRDS doc
+ return nil, nil
+}
+
+func YadisRequest (url string, method string) (resp *http.Response, err os.Error) {
+ resp = nil
+
+ var request = new(http.Request)
+ var client = new(http.Client)
+ var Header = make(http.Header)
+
+ request.Method = method
+ request.RawURL = url
+
+ request.URL , err = http.ParseURL(url)
+ if err != nil {
+ return
+ }
+
+ // Common parameters
+ request.Proto = "HTTP/1.0"
+ request.ProtoMajor = 1
+ request.ProtoMinor = 0
+ request.ContentLength = 0
+ request.Close = true
+
+
+ Header.Add("Accept", "application/xrds+xml")
+ request.Header = Header
+
+ // Follow a maximum of 5 redirections
+ for i := 0; i < 5; i++ {
+ response, err := client.Do(request)
+
+ if response.StatusCode == 301 || response.StatusCode == 302 || response.StatusCode == 303 || response.StatusCode == 307 {
+ location := response.Header.Get("Location")
+ request.RawURL = location
+ request.URL , err = http.ParseURL(location)
+ if err != nil {
+ return
+ }
+ } else {
+ return response, nil
+ }
+ }
+ return nil, os.ErrorString("Too many redirections")
+}
+
+func searchHTMLMetaXRDS(r io.Reader) (string, os.Error) {
parser := xml.NewParser(r)
var token xml.Token
var err os.Error
@@ -47,43 +130,3 @@ func searchHTMLMeta(r io.Reader) (string, os.Error) {
}
return "",os.ErrorString("Value not found")
}
-
-func Yadis(url string) (io.Reader, os.Error) {
- fmt.Printf("Search: %s\n",url)
- headers := map[string] string {
- "Accept": "application/xrds+xml",
- }
- r, err := get (url, headers)
- if (err != nil || r == nil) {
- fmt.Printf("Yadis: Error in GET\n")
- return nil, err
- }
-
- // If it is an XRDS document, parse it and return URI
- content, ok := r.Header["Content-Type"]
- if ok && strings.HasPrefix(content, "application/xrds+xml") {
- fmt.Printf("Document XRDS found\n")
- return r.Body, nil
- }
-
- // If it is an HTML doc search for meta tags
- content, ok = r.Header["Content-Type"]
- if ok && content == "text/html" {
- fmt.Printf("Document HTML found\n")
- url, err := searchHTMLMeta(r.Body)
- if err != nil {
- return nil, err
- }
- return Yadis(url)
- }
-
-
- // If the response contain an X-XRDS-Location header
- xrds, ok := r.Header["X-Xrds-Location"]
- if ok {
- return Yadis(xrds)
- }
-
- // If nothing is found try to parse it as a XRDS doc
- return nil, nil
-}
View
61 yadis_test.go
@@ -0,0 +1,61 @@
+// Copyright 2010 Florian Duraffourg. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openid
+
+import (
+ "testing"
+ "bytes"
+)
+
+// searchHTMLMetaXRDS Test
+
+type searchHTMLMetaXRDSTest struct {
+ in []byte
+ out string
+}
+
+var searchHTMLMetaXRDSTests = []searchHTMLMetaXRDSTest {
+ searchHTMLMetaXRDSTest{[]byte("<html><head><meta http-equiv='X-XRDS-Location' content='location'></meta></head></html>"), "location"},
+ //searchHTMLMetaXRDSTest{[]byte("<html><head><meta>location</meta></head></html>"), "location"},
+}
+
+func TestSearchHTMLMetaXRDS(t *testing.T) {
+ for _, l := range searchHTMLMetaXRDSTests {
+ content, err := searchHTMLMetaXRDS(bytes.NewBuffer(l.in))
+ if err != nil {
+ t.Errorf("searchHTMLMetaXRDS error: %s", err.String())
+ }
+ if ! bytes.Equal([]byte(content), []byte(l.out)) {
+ t.Errorf("searchHTMLMetaXRDS(%s) = %s want %s.", l.in, content, l.out)
+ }
+ }
+}
+
+// Yadis Test
+
+type YadisTest struct {
+ url string
+}
+
+var YadisTests = []YadisTest {
+ YadisTest{"https://www.google.com/accounts/o8/id"},
+ YadisTest{"http://orange.fr/"},
+ YadisTest{"http://yahoo.com/"},
+}
+
+// Test whether the Yadis function returns no errors and a non nil reader
+// Doesn't test the content received
+func TestYadis(t *testing.T) {
+ for _, yt := range YadisTests {
+ var reader, err = Yadis(yt.url)
+ if err != nil {
+ t.Errorf("Yadis(%s) returned a error: %s", yt.url, err.String())
+ continue
+ }
+ if reader == nil {
+ t.Errorf("Yadis(%s) returned a nil reader", yt.url)
+ }
+ }
+}

0 comments on commit 231e73a

Please sign in to comment.
Something went wrong with that request. Please try again.