mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 13:36:38 +00:00 
			
		
		
		
	Login via OpenID-2.0 (#618)
This commit is contained in:
		
				
					committed by
					
						 Kim "BKC" Carlbäcker
						Kim "BKC" Carlbäcker
					
				
			
			
				
	
			
			
			
						parent
						
							0693fbfc00
						
					
				
				
					commit
					71d16f69ff
				
			
							
								
								
									
										13
									
								
								vendor/github.com/yohcop/openid-go/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/yohcop/openid-go/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| Copyright 2015 Yohann Coppel | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
							
								
								
									
										38
									
								
								vendor/github.com/yohcop/openid-go/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/yohcop/openid-go/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| # openid.go | ||||
|  | ||||
| This is a consumer (Relying party) implementation of OpenId 2.0, | ||||
| written in Go. | ||||
|  | ||||
|     go get -u github.com/yohcop/openid-go | ||||
|  | ||||
| [](https://travis-ci.org/yohcop/openid-go) | ||||
|  | ||||
| ## Github | ||||
|  | ||||
| Be awesome! Feel free to clone and use according to the licence. | ||||
| If you make a useful change that can benefit others, send a | ||||
| pull request! This ensures that one version has all the good stuff | ||||
| and doesn't fall behind. | ||||
|  | ||||
| ## Code example | ||||
|  | ||||
| See `_example/` for a simple webserver using the openID | ||||
| implementation. Also, read the comment about the NonceStore towards | ||||
| the top of that file. The example must be run for the openid-go | ||||
| directory, like so: | ||||
|  | ||||
|     go run _example/server.go | ||||
|  | ||||
| ## App Engine | ||||
|  | ||||
| In order to use this on Google App Engine, you need to create an instance with a custom `*http.Client` provided by [urlfetch](https://cloud.google.com/appengine/docs/go/urlfetch/). | ||||
|  | ||||
| ```go | ||||
| oid := openid.NewOpenID(urlfetch.Client(appengine.NewContext(r))) | ||||
| oid.RedirectURL(...) | ||||
| oid.Verify(...) | ||||
| ``` | ||||
|  | ||||
| ## License | ||||
|  | ||||
| Distributed under the [Apache v2.0 license](http://www.apache.org/licenses/LICENSE-2.0.html). | ||||
							
								
								
									
										57
									
								
								vendor/github.com/yohcop/openid-go/discover.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								vendor/github.com/yohcop/openid-go/discover.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| package openid | ||||
|  | ||||
| // 7.3.1.  Discovered Information | ||||
| // Upon successful completion of discovery, the Relying Party will | ||||
| // have one or more sets of the following information (see the | ||||
| // Terminology section for definitions). If more than one set of the | ||||
| // following information has been discovered, the precedence rules | ||||
| // defined in [XRI_Resolution_2.0] are to be applied. | ||||
| //   - OP Endpoint URL | ||||
| //   - Protocol Version | ||||
| // If the end user did not enter an OP Identifier, the following | ||||
| // information will also be present: | ||||
| //   - Claimed Identifier | ||||
| //   - OP-Local Identifier | ||||
| // 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. | ||||
| func Discover(id string) (opEndpoint, opLocalID, claimedID string, err error) { | ||||
| 	return defaultInstance.Discover(id) | ||||
| } | ||||
|  | ||||
| func (oid *OpenID) Discover(id string) (opEndpoint, opLocalID, claimedID string, err error) { | ||||
| 	// From OpenID specs, 7.2: Normalization | ||||
| 	if id, err = Normalize(id); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// From OpenID specs, 7.3: Discovery. | ||||
|  | ||||
| 	// 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. | ||||
|  | ||||
| 	// XRI not supported. | ||||
|  | ||||
| 	// If it is a URL, the Yadis protocol [Yadis] SHALL be first | ||||
| 	// attempted. If it succeeds, the result is again an XRDS | ||||
| 	// document. | ||||
| 	if opEndpoint, opLocalID, err = yadisDiscovery(id, oid.urlGetter); err != 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. | ||||
| 		opEndpoint, opLocalID, claimedID, err = htmlDiscovery(id, oid.urlGetter) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return "", "", "", err | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										69
									
								
								vendor/github.com/yohcop/openid-go/discovery_cache.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								vendor/github.com/yohcop/openid-go/discovery_cache.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| type DiscoveredInfo interface { | ||||
| 	OpEndpoint() string | ||||
| 	OpLocalID() string | ||||
| 	ClaimedID() string | ||||
| 	// ProtocolVersion: it's always openId 2. | ||||
| } | ||||
|  | ||||
| type DiscoveryCache interface { | ||||
| 	Put(id string, info DiscoveredInfo) | ||||
| 	// Return a discovered info, or nil. | ||||
| 	Get(id string) DiscoveredInfo | ||||
| } | ||||
|  | ||||
| type SimpleDiscoveredInfo struct { | ||||
| 	opEndpoint string | ||||
| 	opLocalID  string | ||||
| 	claimedID  string | ||||
| } | ||||
|  | ||||
| func (s *SimpleDiscoveredInfo) OpEndpoint() string { | ||||
| 	return s.opEndpoint | ||||
| } | ||||
|  | ||||
| func (s *SimpleDiscoveredInfo) OpLocalID() string { | ||||
| 	return s.opLocalID | ||||
| } | ||||
|  | ||||
| func (s *SimpleDiscoveredInfo) ClaimedID() string { | ||||
| 	return s.claimedID | ||||
| } | ||||
|  | ||||
| type SimpleDiscoveryCache struct { | ||||
| 	cache map[string]DiscoveredInfo | ||||
| 	mutex *sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewSimpleDiscoveryCache() *SimpleDiscoveryCache { | ||||
| 	return &SimpleDiscoveryCache{cache: map[string]DiscoveredInfo{}, mutex: &sync.Mutex{}} | ||||
| } | ||||
|  | ||||
| func (s *SimpleDiscoveryCache) Put(id string, info DiscoveredInfo) { | ||||
| 	s.mutex.Lock() | ||||
| 	defer s.mutex.Unlock() | ||||
|  | ||||
| 	s.cache[id] = info | ||||
| } | ||||
|  | ||||
| func (s *SimpleDiscoveryCache) Get(id string) DiscoveredInfo { | ||||
| 	s.mutex.Lock() | ||||
| 	defer s.mutex.Unlock() | ||||
|  | ||||
| 	if info, has := s.cache[id]; has { | ||||
| 		return info | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func compareDiscoveredInfo(a DiscoveredInfo, opEndpoint, opLocalID, claimedID string) bool { | ||||
| 	return a != nil && | ||||
| 		a.OpEndpoint() == opEndpoint && | ||||
| 		a.OpLocalID() == opLocalID && | ||||
| 		a.ClaimedID() == claimedID | ||||
| } | ||||
							
								
								
									
										31
									
								
								vendor/github.com/yohcop/openid-go/getter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/yohcop/openid-go/getter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| // Interface that simplifies testing. | ||||
| type httpGetter interface { | ||||
| 	Get(uri string, headers map[string]string) (resp *http.Response, err error) | ||||
| 	Post(uri string, form url.Values) (resp *http.Response, err error) | ||||
| } | ||||
|  | ||||
| type defaultGetter struct { | ||||
| 	client *http.Client | ||||
| } | ||||
|  | ||||
| func (dg *defaultGetter) Get(uri string, headers map[string]string) (resp *http.Response, err error) { | ||||
| 	request, err := http.NewRequest("GET", uri, nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	for h, v := range headers { | ||||
| 		request.Header.Add(h, v) | ||||
| 	} | ||||
| 	return dg.client.Do(request) | ||||
| } | ||||
|  | ||||
| func (dg *defaultGetter) Post(uri string, form url.Values) (resp *http.Response, err error) { | ||||
| 	return dg.client.PostForm(uri, form) | ||||
| } | ||||
							
								
								
									
										77
									
								
								vendor/github.com/yohcop/openid-go/html_discovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								vendor/github.com/yohcop/openid-go/html_discovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io" | ||||
|  | ||||
| 	"golang.org/x/net/html" | ||||
| ) | ||||
|  | ||||
| func htmlDiscovery(id string, getter httpGetter) (opEndpoint, opLocalID, claimedID string, err error) { | ||||
| 	resp, err := getter.Get(id, nil) | ||||
| 	if err != nil { | ||||
| 		return "", "", "", err | ||||
| 	} | ||||
| 	opEndpoint, opLocalID, err = findProviderFromHeadLink(resp.Body) | ||||
| 	return opEndpoint, opLocalID, resp.Request.URL.String(), err | ||||
| } | ||||
|  | ||||
| func findProviderFromHeadLink(input io.Reader) (opEndpoint, opLocalID string, err error) { | ||||
| 	tokenizer := html.NewTokenizer(input) | ||||
| 	inHead := false | ||||
| 	for { | ||||
| 		tt := tokenizer.Next() | ||||
| 		switch tt { | ||||
| 		case html.ErrorToken: | ||||
| 			// Even if the document is malformed after we found a | ||||
| 			// valid <link> tag, ignore and let's be happy with our | ||||
| 			// openid2.provider and potentially openid2.local_id as well. | ||||
| 			if len(opEndpoint) > 0 { | ||||
| 				return | ||||
| 			} | ||||
| 			return "", "", tokenizer.Err() | ||||
| 		case html.StartTagToken, html.EndTagToken, html.SelfClosingTagToken: | ||||
| 			tk := tokenizer.Token() | ||||
| 			if tk.Data == "head" { | ||||
| 				if tt == html.StartTagToken { | ||||
| 					inHead = true | ||||
| 				} else { | ||||
| 					if len(opEndpoint) > 0 { | ||||
| 						return | ||||
| 					} | ||||
| 					return "", "", errors.New( | ||||
| 						"LINK with rel=openid2.provider not found") | ||||
| 				} | ||||
| 			} else if inHead && tk.Data == "link" { | ||||
| 				provider := false | ||||
| 				localID := false | ||||
| 				href := "" | ||||
| 				for _, attr := range tk.Attr { | ||||
| 					if attr.Key == "rel" { | ||||
| 						if attr.Val == "openid2.provider" { | ||||
| 							provider = true | ||||
| 						} else if attr.Val == "openid2.local_id" { | ||||
| 							localID = true | ||||
| 						} else if attr.Val == "openid.server" { | ||||
| 							provider = true | ||||
| 						} | ||||
| 					} else if attr.Key == "href" { | ||||
| 						href = attr.Val | ||||
| 					} | ||||
| 				} | ||||
| 				if provider && !localID && len(href) > 0 { | ||||
| 					opEndpoint = href | ||||
| 				} else if !provider && localID && len(href) > 0 { | ||||
| 					opLocalID = href | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// At this point we should probably have returned either from | ||||
| 	// a closing </head> or a tokenizer error (no </head> found). | ||||
| 	// But just in case. | ||||
| 	if len(opEndpoint) > 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	return "", "", errors.New("LINK rel=openid2.provider not found") | ||||
| } | ||||
							
								
								
									
										87
									
								
								vendor/github.com/yohcop/openid-go/nonce_store.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								vendor/github.com/yohcop/openid-go/nonce_store.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var maxNonceAge = flag.Duration("openid-max-nonce-age", | ||||
| 	60*time.Second, | ||||
| 	"Maximum accepted age for openid nonces. The bigger, the more"+ | ||||
| 		"memory is needed to store used nonces.") | ||||
|  | ||||
| type NonceStore interface { | ||||
| 	// Returns nil if accepted, an error otherwise. | ||||
| 	Accept(endpoint, nonce string) error | ||||
| } | ||||
|  | ||||
| type Nonce struct { | ||||
| 	T time.Time | ||||
| 	S string | ||||
| } | ||||
|  | ||||
| type SimpleNonceStore struct { | ||||
| 	store map[string][]*Nonce | ||||
| 	mutex *sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewSimpleNonceStore() *SimpleNonceStore { | ||||
| 	return &SimpleNonceStore{store: map[string][]*Nonce{}, mutex: &sync.Mutex{}} | ||||
| } | ||||
|  | ||||
| func (d *SimpleNonceStore) Accept(endpoint, nonce string) error { | ||||
| 	// Value: A string 255 characters or less in length, that MUST be | ||||
| 	// unique to this particular successful authentication response. | ||||
| 	if len(nonce) < 20 || len(nonce) > 256 { | ||||
| 		return errors.New("Invalid nonce") | ||||
| 	} | ||||
|  | ||||
| 	// The nonce MUST start with the current time on the server, and MAY | ||||
| 	// contain additional ASCII characters in the range 33-126 inclusive | ||||
| 	// (printable non-whitespace characters), as necessary to make each | ||||
| 	// response unique. The date and time MUST be formatted as specified in | ||||
| 	// section 5.6 of [RFC3339], with the following restrictions: | ||||
|  | ||||
| 	// All times must be in the UTC timezone, indicated with a "Z".  No | ||||
| 	// fractional seconds are allowed For example: | ||||
| 	// 2005-05-15T17:11:51ZUNIQUE | ||||
| 	ts, err := time.Parse(time.RFC3339, nonce[0:20]) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	now := time.Now() | ||||
| 	diff := now.Sub(ts) | ||||
| 	if diff > *maxNonceAge { | ||||
| 		return fmt.Errorf("Nonce too old: %ds", diff.Seconds()) | ||||
| 	} | ||||
|  | ||||
| 	s := nonce[20:] | ||||
|  | ||||
| 	// Meh.. now we have to use a mutex, to protect that map from | ||||
| 	// concurrent access. Could put a go routine in charge of it | ||||
| 	// though. | ||||
| 	d.mutex.Lock() | ||||
| 	defer d.mutex.Unlock() | ||||
|  | ||||
| 	if nonces, hasOp := d.store[endpoint]; hasOp { | ||||
| 		// Delete old nonces while we are at it. | ||||
| 		newNonces := []*Nonce{{ts, s}} | ||||
| 		for _, n := range nonces { | ||||
| 			if n.T == ts && n.S == s { | ||||
| 				// If return early, just ignore the filtered list | ||||
| 				// we have been building so far... | ||||
| 				return errors.New("Nonce already used") | ||||
| 			} | ||||
| 			if now.Sub(n.T) < *maxNonceAge { | ||||
| 				newNonces = append(newNonces, n) | ||||
| 			} | ||||
| 		} | ||||
| 		d.store[endpoint] = newNonces | ||||
| 	} else { | ||||
| 		d.store[endpoint] = []*Nonce{{ts, s}} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										64
									
								
								vendor/github.com/yohcop/openid-go/normalizer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								vendor/github.com/yohcop/openid-go/normalizer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func Normalize(id string) (string, error) { | ||||
| 	id = strings.TrimSpace(id) | ||||
| 	if len(id) == 0 { | ||||
| 		return "", errors.New("No id provided") | ||||
| 	} | ||||
|  | ||||
| 	// 7.2 from openID 2.0 spec. | ||||
|  | ||||
| 	//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(id, "xri://") { | ||||
| 		id = id[6:] | ||||
| 		return id, errors.New("XRI identifiers not supported") | ||||
| 	} | ||||
|  | ||||
| 	// 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], then the input | ||||
| 	// SHOULD be treated as an XRI. | ||||
| 	if b := id[0]; b == '=' || b == '@' || b == '+' || b == '$' || b == '!' { | ||||
| 		return id, errors.New("XRI identifiers not supported") | ||||
| 	} | ||||
|  | ||||
| 	// 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 for | ||||
| 	// more information. | ||||
| 	if !strings.HasPrefix(id, "http://") && !strings.HasPrefix(id, | ||||
| 		"https://") { | ||||
| 		id = "http://" + id | ||||
| 	} | ||||
| 	if fragmentIndex := strings.Index(id, "#"); fragmentIndex != -1 { | ||||
| 		id = id[0:fragmentIndex] | ||||
| 	} | ||||
| 	if u, err := url.ParseRequestURI(id); err != nil { | ||||
| 		return "", err | ||||
| 	} else { | ||||
| 		if u.Host == "" { | ||||
| 			return "", errors.New("Invalid address provided as id") | ||||
| 		} | ||||
| 		if u.Path == "" { | ||||
| 			u.Path = "/" | ||||
| 		} | ||||
| 		id = u.String() | ||||
| 	} | ||||
|  | ||||
| 	// 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] 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. | ||||
| 	return id, nil | ||||
| } | ||||
							
								
								
									
										15
									
								
								vendor/github.com/yohcop/openid-go/openid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/yohcop/openid-go/openid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type OpenID struct { | ||||
| 	urlGetter httpGetter | ||||
| } | ||||
|  | ||||
| func NewOpenID(client *http.Client) *OpenID { | ||||
| 	return &OpenID{urlGetter: &defaultGetter{client: client}} | ||||
| } | ||||
|  | ||||
| var defaultInstance = NewOpenID(http.DefaultClient) | ||||
							
								
								
									
										55
									
								
								vendor/github.com/yohcop/openid-go/redirect.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/yohcop/openid-go/redirect.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func RedirectURL(id, callbackURL, realm string) (string, error) { | ||||
| 	return defaultInstance.RedirectURL(id, callbackURL, realm) | ||||
| } | ||||
|  | ||||
| func (oid *OpenID) RedirectURL(id, callbackURL, realm string) (string, error) { | ||||
| 	opEndpoint, opLocalID, claimedID, err := oid.Discover(id) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return BuildRedirectURL(opEndpoint, opLocalID, claimedID, callbackURL, realm) | ||||
| } | ||||
|  | ||||
| func BuildRedirectURL(opEndpoint, opLocalID, claimedID, returnTo, realm string) (string, error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Add("openid.ns", "http://specs.openid.net/auth/2.0") | ||||
| 	values.Add("openid.mode", "checkid_setup") | ||||
| 	values.Add("openid.return_to", returnTo) | ||||
|  | ||||
| 	// 9.1.  Request Parameters | ||||
| 	// "openid.claimed_id" and "openid.identity" SHALL be either both present or both absent. | ||||
| 	if len(claimedID) > 0 { | ||||
| 		values.Add("openid.claimed_id", claimedID) | ||||
| 		if len(opLocalID) > 0 { | ||||
| 			values.Add("openid.identity", opLocalID) | ||||
| 		} else { | ||||
| 			// If a different OP-Local Identifier is not specified, | ||||
| 			// the claimed identifier MUST be used as the value for openid.identity. | ||||
| 			values.Add("openid.identity", claimedID) | ||||
| 		} | ||||
| 	} else { | ||||
| 		// 7.3.1.  Discovered Information | ||||
| 		// 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. | ||||
| 		values.Add("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select") | ||||
| 		values.Add("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select") | ||||
| 	} | ||||
|  | ||||
| 	if len(realm) > 0 { | ||||
| 		values.Add("openid.realm", realm) | ||||
| 	} | ||||
|  | ||||
| 	if strings.Contains(opEndpoint, "?") { | ||||
| 		return opEndpoint + "&" + values.Encode(), nil | ||||
| 	} | ||||
| 	return opEndpoint + "?" + values.Encode(), nil | ||||
| } | ||||
							
								
								
									
										250
									
								
								vendor/github.com/yohcop/openid-go/verify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								vendor/github.com/yohcop/openid-go/verify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) { | ||||
| 	return defaultInstance.Verify(uri, cache, nonceStore) | ||||
| } | ||||
|  | ||||
| func (oid *OpenID) Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) { | ||||
| 	parsedURL, err := url.Parse(uri) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	values, err := url.ParseQuery(parsedURL.RawQuery) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// 11.  Verifying Assertions | ||||
| 	// When the Relying Party receives a positive assertion, it MUST | ||||
| 	// verify the following before accepting the assertion: | ||||
|  | ||||
| 	// - The value of "openid.signed" contains all the required fields. | ||||
| 	//   (Section 10.1) | ||||
| 	if err = verifySignedFields(values); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// - The signature on the assertion is valid (Section 11.4) | ||||
| 	if err = verifySignature(uri, values, oid.urlGetter); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// - The value of "openid.return_to" matches the URL of the current | ||||
| 	//   request (Section 11.1) | ||||
| 	if err = verifyReturnTo(parsedURL, values); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// - Discovered information matches the information in the assertion | ||||
| 	//   (Section 11.2) | ||||
| 	if err = oid.verifyDiscovered(parsedURL, values, cache); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// - An assertion has not yet been accepted from this OP with the | ||||
| 	//   same value for "openid.response_nonce" (Section 11.3) | ||||
| 	if err = verifyNonce(values, nonceStore); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// If all four of these conditions are met, assertion is now | ||||
| 	// verified. If the assertion contained a Claimed Identifier, the | ||||
| 	// user is now authenticated with that identifier. | ||||
| 	return values.Get("openid.claimed_id"), nil | ||||
| } | ||||
|  | ||||
| // 10.1. Positive Assertions | ||||
| // openid.signed - Comma-separated list of signed fields. | ||||
| // This entry consists of the fields without the "openid." prefix that the signature covers. | ||||
| // This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle", | ||||
| // and if present in the response, "claimed_id" and "identity". | ||||
| func verifySignedFields(vals url.Values) error { | ||||
| 	ok := map[string]bool{ | ||||
| 		"op_endpoint":    false, | ||||
| 		"return_to":      false, | ||||
| 		"response_nonce": false, | ||||
| 		"assoc_handle":   false, | ||||
| 		"claimed_id":     vals.Get("openid.claimed_id") == "", | ||||
| 		"identity":       vals.Get("openid.identity") == "", | ||||
| 	} | ||||
| 	signed := strings.Split(vals.Get("openid.signed"), ",") | ||||
| 	for _, sf := range signed { | ||||
| 		ok[sf] = true | ||||
| 	} | ||||
| 	for k, v := range ok { | ||||
| 		if !v { | ||||
| 			return fmt.Errorf("%v must be signed but isn't", k) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 11.1.  Verifying the Return URL | ||||
| // To verify that the "openid.return_to" URL matches the URL that is processing this assertion: | ||||
| // - The URL scheme, authority, and path MUST be the same between the two | ||||
| //   URLs. | ||||
| // - Any query parameters that are present in the "openid.return_to" URL | ||||
| //   MUST also be present with the same values in the URL of the HTTP | ||||
| //   request the RP received. | ||||
| func verifyReturnTo(uri *url.URL, vals url.Values) error { | ||||
| 	returnTo := vals.Get("openid.return_to") | ||||
| 	rp, err := url.Parse(returnTo) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if uri.Scheme != rp.Scheme || | ||||
| 		uri.Host != rp.Host || | ||||
| 		uri.Path != rp.Path { | ||||
| 		return errors.New( | ||||
| 			"Scheme, host or path don't match in return_to URL") | ||||
| 	} | ||||
| 	qp, err := url.ParseQuery(rp.RawQuery) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return compareQueryParams(qp, vals) | ||||
| } | ||||
|  | ||||
| // Any parameter in q1 must also be present in q2, and values must match. | ||||
| func compareQueryParams(q1, q2 url.Values) error { | ||||
| 	for k := range q1 { | ||||
| 		v1 := q1.Get(k) | ||||
| 		v2 := q2.Get(k) | ||||
| 		if v1 != v2 { | ||||
| 			return fmt.Errorf( | ||||
| 				"URLs query params don't match: Param %s different: %s vs %s", | ||||
| 				k, v1, v2) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (oid *OpenID) verifyDiscovered(uri *url.URL, vals url.Values, cache DiscoveryCache) error { | ||||
| 	version := vals.Get("openid.ns") | ||||
| 	if version != "http://specs.openid.net/auth/2.0" { | ||||
| 		return errors.New("Bad protocol version") | ||||
| 	} | ||||
|  | ||||
| 	endpoint := vals.Get("openid.op_endpoint") | ||||
| 	if len(endpoint) == 0 { | ||||
| 		return errors.New("missing openid.op_endpoint url param") | ||||
| 	} | ||||
| 	localID := vals.Get("openid.identity") | ||||
| 	if len(localID) == 0 { | ||||
| 		return errors.New("no localId to verify") | ||||
| 	} | ||||
| 	claimedID := vals.Get("openid.claimed_id") | ||||
| 	if len(claimedID) == 0 { | ||||
| 		// If no Claimed Identifier is present in the response, the | ||||
| 		// assertion is not about an identifier and the RP MUST NOT use the | ||||
| 		// User-supplied Identifier associated with the current OpenID | ||||
| 		// authentication transaction to identify the user. Extension | ||||
| 		// information in the assertion MAY still be used. | ||||
| 		// --- This library does not support this case. So claimed | ||||
| 		//     identifier must be present. | ||||
| 		return errors.New("no claimed_id to verify") | ||||
| 	} | ||||
|  | ||||
| 	// 11.2.  Verifying Discovered Information | ||||
|  | ||||
| 	// If the Claimed Identifier in the assertion is a URL and contains a | ||||
| 	// fragment, the fragment part and the fragment delimiter character "#" | ||||
| 	// MUST NOT be used for the purposes of verifying the discovered | ||||
| 	// information. | ||||
| 	claimedIDVerify := claimedID | ||||
| 	if fragmentIndex := strings.Index(claimedID, "#"); fragmentIndex != -1 { | ||||
| 		claimedIDVerify = claimedID[0:fragmentIndex] | ||||
| 	} | ||||
|  | ||||
| 	// If the Claimed Identifier is included in the assertion, it | ||||
| 	// MUST have been discovered by the Relying Party and the | ||||
| 	// information in the assertion MUST be present in the | ||||
| 	// discovered information. The Claimed Identifier MUST NOT be an | ||||
| 	// OP Identifier. | ||||
| 	if discovered := cache.Get(claimedIDVerify); discovered != nil && | ||||
| 		discovered.OpEndpoint() == endpoint && | ||||
| 		discovered.OpLocalID() == localID && | ||||
| 		discovered.ClaimedID() == claimedIDVerify { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// If the Claimed Identifier was not previously discovered by the | ||||
| 	// Relying Party (the "openid.identity" in the request was | ||||
| 	// "http://specs.openid.net/auth/2.0/identifier_select" or a different | ||||
| 	// Identifier, or if the OP is sending an unsolicited positive | ||||
| 	// assertion), the Relying Party MUST perform discovery on the Claimed | ||||
| 	// Identifier in the response to make sure that the OP is authorized to | ||||
| 	// make assertions about the Claimed Identifier. | ||||
| 	if ep, _, _, err := oid.Discover(claimedID); err == nil { | ||||
| 		if ep == endpoint { | ||||
| 			// This claimed ID points to the same endpoint, therefore this | ||||
| 			// endpoint is authorized to make assertions about that claimed ID. | ||||
| 			// TODO: There may be multiple endpoints found during discovery. | ||||
| 			// They should all be checked. | ||||
| 			cache.Put(claimedIDVerify, &SimpleDiscoveredInfo{opEndpoint: endpoint, opLocalID: localID, claimedID: claimedIDVerify}) | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return errors.New("Could not verify the claimed ID") | ||||
| } | ||||
|  | ||||
| func verifyNonce(vals url.Values, store NonceStore) error { | ||||
| 	nonce := vals.Get("openid.response_nonce") | ||||
| 	endpoint := vals.Get("openid.op_endpoint") | ||||
| 	return store.Accept(endpoint, nonce) | ||||
| } | ||||
|  | ||||
| func verifySignature(uri string, vals url.Values, getter httpGetter) error { | ||||
| 	// To have the signature verification performed by the OP, the | ||||
| 	// Relying Party sends a direct request to the OP. To verify the | ||||
| 	// signature, the OP uses a private association that was generated | ||||
| 	// when it issued the positive assertion. | ||||
|  | ||||
| 	// 11.4.2.1.  Request Parameters | ||||
| 	params := make(url.Values) | ||||
| 	// openid.mode: Value: "check_authentication" | ||||
| 	params.Add("openid.mode", "check_authentication") | ||||
| 	// Exact copies of all fields from the authentication response, | ||||
| 	// except for "openid.mode". | ||||
| 	for k, vs := range vals { | ||||
| 		if k == "openid.mode" { | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, v := range vs { | ||||
| 			params.Add(k, v) | ||||
| 		} | ||||
| 	} | ||||
| 	resp, err := getter.Post(vals.Get("openid.op_endpoint"), params) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 	content, err := ioutil.ReadAll(resp.Body) | ||||
| 	response := string(content) | ||||
| 	lines := strings.Split(response, "\n") | ||||
|  | ||||
| 	isValid := false | ||||
| 	nsValid := false | ||||
| 	for _, l := range lines { | ||||
| 		if l == "is_valid:true" { | ||||
| 			isValid = true | ||||
| 		} else if l == "ns:http://specs.openid.net/auth/2.0" { | ||||
| 			nsValid = true | ||||
| 		} | ||||
| 	} | ||||
| 	if isValid && nsValid { | ||||
| 		// Yay ! | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return errors.New("Could not verify assertion with provider") | ||||
| } | ||||
							
								
								
									
										83
									
								
								vendor/github.com/yohcop/openid-go/xrds.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								vendor/github.com/yohcop/openid-go/xrds.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // TODO: As per 11.2 in openid 2 specs, a service may have multiple | ||||
| //       URIs. We don't care for discovery really, but we do care for | ||||
| //       verification though. | ||||
| type XrdsIdentifier struct { | ||||
| 	Type     []string `xml:"Type"` | ||||
| 	URI      string   `xml:"URI"` | ||||
| 	LocalID  string   `xml:"LocalID"` | ||||
| 	Priority int      `xml:"priority,attr"` | ||||
| } | ||||
|  | ||||
| type Xrd struct { | ||||
| 	Service []*XrdsIdentifier `xml:"Service"` | ||||
| } | ||||
|  | ||||
| type XrdsDocument struct { | ||||
| 	XMLName xml.Name `xml:"XRDS"` | ||||
| 	Xrd     *Xrd     `xml:"XRD"` | ||||
| } | ||||
|  | ||||
| func parseXrds(input []byte) (opEndpoint, opLocalID string, err error) { | ||||
| 	xrdsDoc := &XrdsDocument{} | ||||
| 	err = xml.Unmarshal(input, xrdsDoc) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if xrdsDoc.Xrd == nil { | ||||
| 		return "", "", errors.New("XRDS document missing XRD tag") | ||||
| 	} | ||||
|  | ||||
| 	// 7.3.2.2.  Extracting Authentication Data | ||||
| 	// Once the Relying Party has obtained an XRDS document, it | ||||
| 	// MUST first search the document (following the rules | ||||
| 	// described in [XRI_Resolution_2.0]) for an OP Identifier | ||||
| 	// Element. If none is found, the RP will search for a Claimed | ||||
| 	// Identifier Element. | ||||
| 	for _, service := range xrdsDoc.Xrd.Service { | ||||
| 		// 7.3.2.1.1.  OP Identifier Element | ||||
| 		// An OP Identifier Element is an <xrd:Service> element with the | ||||
| 		// following information: | ||||
| 		// An <xrd:Type> tag whose text content is | ||||
| 		//     "http://specs.openid.net/auth/2.0/server". | ||||
| 		// An <xrd:URI> tag whose text content is the OP Endpoint URL | ||||
| 		if service.hasType("http://specs.openid.net/auth/2.0/server") { | ||||
| 			opEndpoint = strings.TrimSpace(service.URI) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	for _, service := range xrdsDoc.Xrd.Service { | ||||
| 		// 7.3.2.1.2.  Claimed Identifier Element | ||||
| 		// A Claimed Identifier Element is an <xrd:Service> element | ||||
| 		// with the following information: | ||||
| 		// An <xrd:Type> tag whose text content is | ||||
| 		//     "http://specs.openid.net/auth/2.0/signon". | ||||
| 		// An <xrd:URI> tag whose text content is the OP Endpoint | ||||
| 		//     URL. | ||||
| 		// An <xrd:LocalID> tag (optional) whose text content is the | ||||
| 		//     OP-Local Identifier. | ||||
| 		if service.hasType("http://specs.openid.net/auth/2.0/signon") { | ||||
| 			opEndpoint = strings.TrimSpace(service.URI) | ||||
| 			opLocalID = strings.TrimSpace(service.LocalID) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return "", "", errors.New("Could not find a compatible service") | ||||
| } | ||||
|  | ||||
| func (xrdsi *XrdsIdentifier) hasType(tpe string) bool { | ||||
| 	for _, t := range xrdsi.Type { | ||||
| 		if t == tpe { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										119
									
								
								vendor/github.com/yohcop/openid-go/yadis_discovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								vendor/github.com/yohcop/openid-go/yadis_discovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| package openid | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/net/html" | ||||
| ) | ||||
|  | ||||
| var yadisHeaders = map[string]string{ | ||||
| 	"Accept": "application/xrds+xml"} | ||||
|  | ||||
| func yadisDiscovery(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) { | ||||
| 	// Section 6.2.4 of Yadis 1.0 specifications. | ||||
| 	// The Yadis Protocol is initiated by the Relying Party Agent | ||||
| 	// with an initial HTTP request using the Yadis URL. | ||||
|  | ||||
| 	// This request MUST be either a GET or a HEAD request. | ||||
|  | ||||
| 	// A GET or HEAD request MAY include an HTTP Accept | ||||
| 	// request-header (HTTP 14.1) specifying MIME media type, | ||||
| 	// application/xrds+xml. | ||||
| 	resp, err := getter.Get(id, yadisHeaders) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// Section 6.2.5 from Yadis 1.0 spec: Response | ||||
|  | ||||
| 	contentType := resp.Header.Get("Content-Type") | ||||
|  | ||||
| 	// The response MUST be one of: | ||||
| 	// (see 6.2.6 for precedence) | ||||
| 	if l := resp.Header.Get("X-XRDS-Location"); l != "" { | ||||
| 		// 2. HTTP response-headers that include an X-XRDS-Location | ||||
| 		// response-header, together with a document | ||||
| 		return getYadisResourceDescriptor(l, getter) | ||||
| 	} else if strings.Contains(contentType, "text/html") { | ||||
| 		// 1. An HTML document with a <head> element that includes a | ||||
| 		// <meta> element with http-equiv attribute, X-XRDS-Location, | ||||
|  | ||||
| 		metaContent, err := findMetaXrdsLocation(resp.Body) | ||||
| 		if err == nil { | ||||
| 			return getYadisResourceDescriptor(metaContent, getter) | ||||
| 		} | ||||
| 		return "", "", err | ||||
| 	} else if strings.Contains(contentType, "application/xrds+xml") { | ||||
| 		// 4. A document of MIME media type, application/xrds+xml. | ||||
| 		body, err := ioutil.ReadAll(resp.Body) | ||||
| 		if err == nil { | ||||
| 			return parseXrds(body) | ||||
| 		} | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 	// 3. HTTP response-headers only, which MAY include an | ||||
| 	// X-XRDS-Location response-header, a content-type | ||||
| 	// response-header specifying MIME media type, | ||||
| 	// application/xrds+xml, or both. | ||||
| 	//   (this is handled by one of the 2 previous if statements) | ||||
| 	return "", "", errors.New("No expected header, or content type") | ||||
| } | ||||
|  | ||||
| // Similar as above, but we expect an absolute Yadis document URL. | ||||
| func getYadisResourceDescriptor(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) { | ||||
| 	resp, err := getter.Get(id, yadisHeaders) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 	// 4. A document of MIME media type, application/xrds+xml. | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err == nil { | ||||
| 		return parseXrds(body) | ||||
| 	} | ||||
| 	return "", "", err | ||||
| } | ||||
|  | ||||
| // Search for | ||||
| // <head> | ||||
| //    <meta http-equiv="X-XRDS-Location" content="...."> | ||||
| func findMetaXrdsLocation(input io.Reader) (location string, err error) { | ||||
| 	tokenizer := html.NewTokenizer(input) | ||||
| 	inHead := false | ||||
| 	for { | ||||
| 		tt := tokenizer.Next() | ||||
| 		switch tt { | ||||
| 		case html.ErrorToken: | ||||
| 			return "", tokenizer.Err() | ||||
| 		case html.StartTagToken, html.EndTagToken: | ||||
| 			tk := tokenizer.Token() | ||||
| 			if tk.Data == "head" { | ||||
| 				if tt == html.StartTagToken { | ||||
| 					inHead = true | ||||
| 				} else { | ||||
| 					return "", errors.New("Meta X-XRDS-Location not found") | ||||
| 				} | ||||
| 			} else if inHead && tk.Data == "meta" { | ||||
| 				ok := false | ||||
| 				content := "" | ||||
| 				for _, attr := range tk.Attr { | ||||
| 					if attr.Key == "http-equiv" && | ||||
| 						strings.ToLower(attr.Val) == "x-xrds-location" { | ||||
| 						ok = true | ||||
| 					} else if attr.Key == "content" { | ||||
| 						content = attr.Val | ||||
| 					} | ||||
| 				} | ||||
| 				if ok && len(content) > 0 { | ||||
| 					return content, nil | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "", errors.New("Meta X-XRDS-Location not found") | ||||
| } | ||||
		Reference in New Issue
	
	Block a user