Login via OpenID-2.0 (#618)

This commit is contained in:
Sandro Santilli 2017-03-17 15:16:08 +01:00 committed by Kim "BKC" Carlbäcker
parent 0693fbfc00
commit 71d16f69ff
44 changed files with 2298 additions and 57 deletions

13
vendor/github.com/yohcop/openid-go/LICENSE generated vendored Normal file
View 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
View 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
[![Build Status](https://travis-ci.org/yohcop/openid-go.svg?branch=master)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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")
}

6
vendor/vendor.json vendored
View file

@ -1162,6 +1162,12 @@
"path": "golang.org/x/crypto/cast5",
"revision": "b8a2a83acfe6e6770b75de42d5ff4c67596675c0",
"revisionTime": "2017-01-13T19:21:00Z"
},
{
"checksumSHA1": "pkrINpw0HkmO+18SdtSjje9MB9g=",
"path": "github.com/yohcop/openid-go",
"revision": "2c050d2dae5345c417db301f11fda6fbf5ad0f0a",
"revisionTime": "2016-09-14T08:04:27Z"
},
{
"checksumSHA1": "dwOedwBJ1EIK9+S3t108Bx054Y8=",