replace zxq.co/ripple/hanayo

This commit is contained in:
Alicia
2019-02-23 13:29:15 +00:00
commit c3d206c173
5871 changed files with 1353715 additions and 0 deletions

2
vendor/github.com/RangelReale/osin/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
*.exe
/src

7
vendor/github.com/RangelReale/osin/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,7 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- tip

19
vendor/github.com/RangelReale/osin/CHANGELOG generated vendored Normal file
View File

@@ -0,0 +1,19 @@
2014-06-25
==========
* BREAKING CHANGES:
- Storage interface has 2 new methods, Clone and Close, to better support storages
that need to clone / close in each connection (mgo)
- Client was changed to be an interface instead of an struct. Because of that,
the Storage interface also had to change, as interface is already a pointer.
- HOW TO FIX YOUR CODE:
+ In your Storage, add a Clone function returning itself, and a do nothing Close.
+ In your Storage, replace all *osin.Client with osin.Client (remove the pointer reference)
+ If you used the osin.Client struct directly in your code, change it to osin.DefaultClient,
which is a struct with the same fields that implements the interface.
+ Change all accesses using osin.Client to use the methods instead of the fields directly.
+ You MUST defer Response.Close in all your http handlers, otherwise some
Storages may not clean correctly.
resp := server.NewResponse()
defer resp.Close()

9
vendor/github.com/RangelReale/osin/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,9 @@
Copyright (c) 2013, Rangel Reale
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 the SIB IT 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 HOLDER 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.

112
vendor/github.com/RangelReale/osin/README.md generated vendored Normal file
View File

@@ -0,0 +1,112 @@
OSIN
====
[![GoDoc](https://godoc.org/github.com/RangelReale/osin?status.svg)](https://godoc.org/github.com/RangelReale/osin)
Golang OAuth2 server library
----------------------------
OSIN is an OAuth2 server library for the Go language, as specified at
http://tools.ietf.org/html/rfc6749 and http://tools.ietf.org/html/draft-ietf-oauth-v2-10.
It also includes support for PKCE, as specified at https://tools.ietf.org/html/rfc7636,
which increases security for code-exchange flows for public OAuth clients.
Using it, you can build your own OAuth2 authentication service.
The library implements the majority of the specification, like authorization and token endpoints, and authorization code, implicit, resource owner and client credentials grant types.
### Example Server
````go
import (
"github.com/RangelReale/osin"
ex "github.com/RangelReale/osin/example"
)
// ex.NewTestStorage implements the "osin.Storage" interface
server := osin.NewServer(osin.NewServerConfig(), ex.NewTestStorage())
// Authorization code endpoint
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
// HANDLE LOGIN PAGE HERE
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
osin.OutputJSON(resp, w, r)
})
// Access token endpoint
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
}
osin.OutputJSON(resp, w, r)
})
http.ListenAndServe(":14000", nil)
````
### Example Access
Open in your web browser:
````
http://localhost:14000/authorize?response_type=code&client_id=1234&redirect_uri=http%3A%2F%2Flocalhost%3A14000%2Fappauth%2Fcode
````
### Storage backends
There is a mock available at [example/teststorage.go](/example/teststorage.go) which you can use as a guide for writing your own.
You might want to check out other implementations for common database management systems as well:
* [PostgreSQL](https://github.com/ory-am/osin-storage)
* [MongoDB](https://github.com/martint17r/osin-mongo-storage)
* [RethinkDB](https://github.com/ahmet/osin-rethinkdb)
* [DynamoDB](https://github.com/uniplaces/osin-dynamodb)
* [Couchbase](https://github.com/elgris/osin-couchbase-storage)
* [MySQL](https://github.com/felipeweb/osin-mysql)
* [Redis](https://github.com/ShaleApps/osinredis)
### License
The code is licensed using "New BSD" license.
### Author
Rangel Reale
rangelreale@gmail.com
### Changes
2014-06-25
==========
* BREAKING CHANGES:
- Storage interface has 2 new methods, Clone and Close, to better support storages
that need to clone / close in each connection (mgo)
- Client was changed to be an interface instead of an struct. Because of that,
the Storage interface also had to change, as interface is already a pointer.
- HOW TO FIX YOUR CODE:
+ In your Storage, add a Clone function returning itself, and a do nothing Close.
+ In your Storage, replace all *osin.Client with osin.Client (remove the pointer reference)
+ If you used the osin.Client struct directly in your code, change it to osin.DefaultClient,
which is a struct with the same fields that implements the interface.
+ Change all accesses using osin.Client to use the methods instead of the fields directly.
+ You MUST defer Response.Close in all your http handlers, otherwise some
Storages may not clean correctly.
resp := server.NewResponse()
defer resp.Close()

562
vendor/github.com/RangelReale/osin/access.go generated vendored Normal file
View File

@@ -0,0 +1,562 @@
package osin
import (
"crypto/sha256"
"encoding/base64"
"errors"
"net/http"
"strings"
"time"
)
// AccessRequestType is the type for OAuth param `grant_type`
type AccessRequestType string
const (
AUTHORIZATION_CODE AccessRequestType = "authorization_code"
REFRESH_TOKEN AccessRequestType = "refresh_token"
PASSWORD AccessRequestType = "password"
CLIENT_CREDENTIALS AccessRequestType = "client_credentials"
ASSERTION AccessRequestType = "assertion"
IMPLICIT AccessRequestType = "__implicit"
)
// AccessRequest is a request for access tokens
type AccessRequest struct {
Type AccessRequestType
Code string
Client Client
AuthorizeData *AuthorizeData
AccessData *AccessData
// Force finish to use this access data, to allow access data reuse
ForceAccessData *AccessData
RedirectUri string
Scope string
Username string
Password string
AssertionType string
Assertion string
// Set if request is authorized
Authorized bool
// Token expiration in seconds. Change if different from default
Expiration int32
// Set if a refresh token should be generated
GenerateRefresh bool
// Data to be passed to storage. Not used by the library.
UserData interface{}
// HttpRequest *http.Request for special use
HttpRequest *http.Request
// Optional code_verifier as described in rfc7636
CodeVerifier string
}
// AccessData represents an access grant (tokens, expiration, client, etc)
type AccessData struct {
// Client information
Client Client
// Authorize data, for authorization code
AuthorizeData *AuthorizeData
// Previous access data, for refresh token
AccessData *AccessData
// Access token
AccessToken string
// Refresh Token. Can be blank
RefreshToken string
// Token expiration in seconds
ExpiresIn int32
// Requested scope
Scope string
// Redirect Uri from request
RedirectUri string
// Date created
CreatedAt time.Time
// Data to be passed to storage. Not used by the library.
UserData interface{}
}
// IsExpired returns true if access expired
func (d *AccessData) IsExpired() bool {
return d.IsExpiredAt(time.Now())
}
// IsExpiredAt returns true if access expires at time 't'
func (d *AccessData) IsExpiredAt(t time.Time) bool {
return d.ExpireAt().Before(t)
}
// ExpireAt returns the expiration date
func (d *AccessData) ExpireAt() time.Time {
return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
}
// AccessTokenGen generates access tokens
type AccessTokenGen interface {
GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error)
}
// HandleAccessRequest is the http.HandlerFunc for handling access token requests
func (s *Server) HandleAccessRequest(w *Response, r *http.Request) *AccessRequest {
// Only allow GET or POST
if r.Method == "GET" {
if !s.Config.AllowGetAccessRequest {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = errors.New("Request must be POST")
return nil
}
} else if r.Method != "POST" {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = errors.New("Request must be POST")
return nil
}
err := r.ParseForm()
if err != nil {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = err
return nil
}
grantType := AccessRequestType(r.Form.Get("grant_type"))
if s.Config.AllowedAccessTypes.Exists(grantType) {
switch grantType {
case AUTHORIZATION_CODE:
return s.handleAuthorizationCodeRequest(w, r)
case REFRESH_TOKEN:
return s.handleRefreshTokenRequest(w, r)
case PASSWORD:
return s.handlePasswordRequest(w, r)
case CLIENT_CREDENTIALS:
return s.handleClientCredentialsRequest(w, r)
case ASSERTION:
return s.handleAssertionRequest(w, r)
}
}
w.SetError(E_UNSUPPORTED_GRANT_TYPE, "")
return nil
}
func (s *Server) handleAuthorizationCodeRequest(w *Response, r *http.Request) *AccessRequest {
// get client authentication
auth := getClientAuth(w, r, s.Config.AllowClientSecretInParams)
if auth == nil {
return nil
}
// generate access token
ret := &AccessRequest{
Type: AUTHORIZATION_CODE,
Code: r.Form.Get("code"),
CodeVerifier: r.Form.Get("code_verifier"),
RedirectUri: r.Form.Get("redirect_uri"),
GenerateRefresh: true,
Expiration: s.Config.AccessExpiration,
HttpRequest: r,
}
// "code" is required
if ret.Code == "" {
w.SetError(E_INVALID_GRANT, "")
return nil
}
// must have a valid client
if ret.Client = getClient(auth, w.Storage, w); ret.Client == nil {
return nil
}
// must be a valid authorization code
var err error
ret.AuthorizeData, err = w.Storage.LoadAuthorize(ret.Code)
if err != nil {
w.SetError(E_INVALID_GRANT, "")
w.InternalError = err
return nil
}
if ret.AuthorizeData == nil {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AuthorizeData.Client == nil {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AuthorizeData.Client.GetRedirectUri() == "" {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AuthorizeData.IsExpiredAt(s.Now()) {
w.SetError(E_INVALID_GRANT, "")
return nil
}
// code must be from the client
if ret.AuthorizeData.Client.GetId() != ret.Client.GetId() {
w.SetError(E_INVALID_GRANT, "")
return nil
}
// check redirect uri
if ret.RedirectUri == "" {
ret.RedirectUri = FirstUri(ret.Client.GetRedirectUri(), s.Config.RedirectUriSeparator)
}
if err = ValidateUriList(ret.Client.GetRedirectUri(), ret.RedirectUri, s.Config.RedirectUriSeparator); err != nil {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = err
return nil
}
if ret.AuthorizeData.RedirectUri != ret.RedirectUri {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = errors.New("Redirect uri is different")
return nil
}
// Verify PKCE, if present in the authorization data
if len(ret.AuthorizeData.CodeChallenge) > 0 {
// https://tools.ietf.org/html/rfc7636#section-4.1
if matched := pkceMatcher.MatchString(ret.CodeVerifier); !matched {
w.SetError(E_INVALID_REQUEST, "code_verifier invalid (rfc7636)")
w.InternalError = errors.New("code_verifier has invalid format")
return nil
}
// https: //tools.ietf.org/html/rfc7636#section-4.6
codeVerifier := ""
switch ret.AuthorizeData.CodeChallengeMethod {
case "", PKCE_PLAIN:
codeVerifier = ret.CodeVerifier
case PKCE_S256:
hash := sha256.Sum256([]byte(ret.CodeVerifier))
codeVerifier = base64.RawURLEncoding.EncodeToString(hash[:])
default:
w.SetError(E_INVALID_REQUEST, "code_challenge_method transform algorithm not supported (rfc7636)")
return nil
}
if codeVerifier != ret.AuthorizeData.CodeChallenge {
w.SetError(E_INVALID_GRANT, "code_verifier invalid (rfc7636)")
w.InternalError = errors.New("code_verifier failed comparison with code_challenge")
return nil
}
}
// set rest of data
ret.Scope = ret.AuthorizeData.Scope
ret.UserData = ret.AuthorizeData.UserData
return ret
}
func extraScopes(access_scopes, refresh_scopes string) bool {
access_scopes_list := strings.Split(access_scopes, " ")
refresh_scopes_list := strings.Split(refresh_scopes, " ")
access_map := make(map[string]int)
for _, scope := range access_scopes_list {
if scope == "" {
continue
}
access_map[scope] = 1
}
for _, scope := range refresh_scopes_list {
if scope == "" {
continue
}
if _, ok := access_map[scope]; !ok {
return true
}
}
return false
}
func (s *Server) handleRefreshTokenRequest(w *Response, r *http.Request) *AccessRequest {
// get client authentication
auth := getClientAuth(w, r, s.Config.AllowClientSecretInParams)
if auth == nil {
return nil
}
// generate access token
ret := &AccessRequest{
Type: REFRESH_TOKEN,
Code: r.Form.Get("refresh_token"),
Scope: r.Form.Get("scope"),
GenerateRefresh: true,
Expiration: s.Config.AccessExpiration,
HttpRequest: r,
}
// "refresh_token" is required
if ret.Code == "" {
w.SetError(E_INVALID_GRANT, "")
return nil
}
// must have a valid client
if ret.Client = getClient(auth, w.Storage, w); ret.Client == nil {
return nil
}
// must be a valid refresh code
var err error
ret.AccessData, err = w.Storage.LoadRefresh(ret.Code)
if err != nil {
w.SetError(E_INVALID_GRANT, "")
w.InternalError = err
return nil
}
if ret.AccessData == nil {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AccessData.Client == nil {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AccessData.Client.GetRedirectUri() == "" {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
// client must be the same as the previous token
if ret.AccessData.Client.GetId() != ret.Client.GetId() {
w.SetError(E_INVALID_CLIENT, "")
w.InternalError = errors.New("Client id must be the same from previous token")
return nil
}
// set rest of data
ret.RedirectUri = ret.AccessData.RedirectUri
ret.UserData = ret.AccessData.UserData
if ret.Scope == "" {
ret.Scope = ret.AccessData.Scope
}
if extraScopes(ret.AccessData.Scope, ret.Scope) {
w.SetError(E_ACCESS_DENIED, "")
w.InternalError = errors.New("the requested scope must not include any scope not originally granted by the resource owner")
return nil
}
return ret
}
func (s *Server) handlePasswordRequest(w *Response, r *http.Request) *AccessRequest {
// get client authentication
auth := getClientAuth(w, r, s.Config.AllowClientSecretInParams)
if auth == nil {
return nil
}
// generate access token
ret := &AccessRequest{
Type: PASSWORD,
Username: r.Form.Get("username"),
Password: r.Form.Get("password"),
Scope: r.Form.Get("scope"),
GenerateRefresh: true,
Expiration: s.Config.AccessExpiration,
HttpRequest: r,
}
// "username" and "password" is required
if ret.Username == "" || ret.Password == "" {
w.SetError(E_INVALID_GRANT, "")
return nil
}
// must have a valid client
if ret.Client = getClient(auth, w.Storage, w); ret.Client == nil {
return nil
}
// set redirect uri
ret.RedirectUri = FirstUri(ret.Client.GetRedirectUri(), s.Config.RedirectUriSeparator)
return ret
}
func (s *Server) handleClientCredentialsRequest(w *Response, r *http.Request) *AccessRequest {
// get client authentication
auth := getClientAuth(w, r, s.Config.AllowClientSecretInParams)
if auth == nil {
return nil
}
// generate access token
ret := &AccessRequest{
Type: CLIENT_CREDENTIALS,
Scope: r.Form.Get("scope"),
GenerateRefresh: false,
Expiration: s.Config.AccessExpiration,
HttpRequest: r,
}
// must have a valid client
if ret.Client = getClient(auth, w.Storage, w); ret.Client == nil {
return nil
}
// set redirect uri
ret.RedirectUri = FirstUri(ret.Client.GetRedirectUri(), s.Config.RedirectUriSeparator)
return ret
}
func (s *Server) handleAssertionRequest(w *Response, r *http.Request) *AccessRequest {
// get client authentication
auth := getClientAuth(w, r, s.Config.AllowClientSecretInParams)
if auth == nil {
return nil
}
// generate access token
ret := &AccessRequest{
Type: ASSERTION,
Scope: r.Form.Get("scope"),
AssertionType: r.Form.Get("assertion_type"),
Assertion: r.Form.Get("assertion"),
GenerateRefresh: false, // assertion should NOT generate a refresh token, per the RFC
Expiration: s.Config.AccessExpiration,
HttpRequest: r,
}
// "assertion_type" and "assertion" is required
if ret.AssertionType == "" || ret.Assertion == "" {
w.SetError(E_INVALID_GRANT, "")
return nil
}
// must have a valid client
if ret.Client = getClient(auth, w.Storage, w); ret.Client == nil {
return nil
}
// set redirect uri
ret.RedirectUri = FirstUri(ret.Client.GetRedirectUri(), s.Config.RedirectUriSeparator)
return ret
}
func (s *Server) FinishAccessRequest(w *Response, r *http.Request, ar *AccessRequest) {
// don't process if is already an error
if w.IsError {
return
}
redirectUri := r.Form.Get("redirect_uri")
// Get redirect uri from AccessRequest if it's there (e.g., refresh token request)
if ar.RedirectUri != "" {
redirectUri = ar.RedirectUri
}
if ar.Authorized {
var ret *AccessData
var err error
if ar.ForceAccessData == nil {
// generate access token
ret = &AccessData{
Client: ar.Client,
AuthorizeData: ar.AuthorizeData,
AccessData: ar.AccessData,
RedirectUri: redirectUri,
CreatedAt: s.Now(),
ExpiresIn: ar.Expiration,
UserData: ar.UserData,
Scope: ar.Scope,
}
// generate access token
ret.AccessToken, ret.RefreshToken, err = s.AccessTokenGen.GenerateAccessToken(ret, ar.GenerateRefresh)
if err != nil {
w.SetError(E_SERVER_ERROR, "")
w.InternalError = err
return
}
} else {
ret = ar.ForceAccessData
}
// save access token
if err = w.Storage.SaveAccess(ret); err != nil {
w.SetError(E_SERVER_ERROR, "")
w.InternalError = err
return
}
// remove authorization token
if ret.AuthorizeData != nil {
w.Storage.RemoveAuthorize(ret.AuthorizeData.Code)
}
// remove previous access token
if ret.AccessData != nil && !s.Config.RetainTokenAfterRefresh {
if ret.AccessData.RefreshToken != "" {
w.Storage.RemoveRefresh(ret.AccessData.RefreshToken)
}
w.Storage.RemoveAccess(ret.AccessData.AccessToken)
}
// output data
w.Output["access_token"] = ret.AccessToken
w.Output["token_type"] = s.Config.TokenType
w.Output["expires_in"] = ret.ExpiresIn
if ret.RefreshToken != "" {
w.Output["refresh_token"] = ret.RefreshToken
}
if ret.Scope != "" {
w.Output["scope"] = ret.Scope
}
} else {
w.SetError(E_ACCESS_DENIED, "")
}
}
// Helper Functions
// getClient looks up and authenticates the basic auth using the given
// storage. Sets an error on the response if auth fails or a server error occurs.
func getClient(auth *BasicAuth, storage Storage, w *Response) Client {
client, err := storage.GetClient(auth.Username)
if err == ErrNotFound {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if err != nil {
w.SetError(E_SERVER_ERROR, "")
w.InternalError = err
return nil
}
if client == nil {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if !CheckClientSecret(client, auth.Password) {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if client.GetRedirectUri() == "" {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
return client
}

489
vendor/github.com/RangelReale/osin/access_test.go generated vendored Normal file
View File

@@ -0,0 +1,489 @@
package osin
import (
"net/http"
"net/url"
"testing"
"time"
)
func TestAccessAuthorizationCode(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAccessTypes = AllowedAccessType{AUTHORIZATION_CODE}
server := NewServer(sconfig, NewTestingStorage())
server.AccessTokenGen = &TestingAccessTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("1234", "aabbccdd")
req.Form = make(url.Values)
req.Form.Set("grant_type", string(AUTHORIZATION_CODE))
req.Form.Set("code", "9999")
req.Form.Set("state", "a")
req.PostForm = make(url.Values)
if ar := server.HandleAccessRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("Unexpected access token: %s", d)
}
if d := resp.Output["refresh_token"]; d != "r1" {
t.Fatalf("Unexpected refresh token: %s", d)
}
}
func TestAccessRefreshToken(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAccessTypes = AllowedAccessType{REFRESH_TOKEN}
server := NewServer(sconfig, NewTestingStorage())
server.AccessTokenGen = &TestingAccessTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("1234", "aabbccdd")
req.Form = make(url.Values)
req.Form.Set("grant_type", string(REFRESH_TOKEN))
req.Form.Set("refresh_token", "r9999")
req.Form.Set("state", "a")
req.PostForm = make(url.Values)
if ar := server.HandleAccessRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if _, err := server.Storage.LoadRefresh("r9999"); err == nil {
t.Fatalf("token was not deleted")
}
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("Unexpected access token: %s", d)
}
if d := resp.Output["refresh_token"]; d != "r1" {
t.Fatalf("Unexpected refresh token: %s", d)
}
}
func TestAccessRefreshTokenSaveToken(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAccessTypes = AllowedAccessType{REFRESH_TOKEN}
server := NewServer(sconfig, NewTestingStorage())
server.AccessTokenGen = &TestingAccessTokenGen{}
server.Config.RetainTokenAfterRefresh = true
resp := server.NewResponse()
req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("1234", "aabbccdd")
req.Form = make(url.Values)
req.Form.Set("grant_type", string(REFRESH_TOKEN))
req.Form.Set("refresh_token", "r9999")
req.Form.Set("state", "a")
req.PostForm = make(url.Values)
if ar := server.HandleAccessRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if _, err := server.Storage.LoadRefresh("r9999"); err != nil {
t.Fatalf("token incorrectly deleted: %s", err.Error())
}
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("Unexpected access token: %s", d)
}
if d := resp.Output["refresh_token"]; d != "r1" {
t.Fatalf("Unexpected refresh token: %s", d)
}
}
func TestAccessPassword(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAccessTypes = AllowedAccessType{PASSWORD}
server := NewServer(sconfig, NewTestingStorage())
server.AccessTokenGen = &TestingAccessTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("1234", "aabbccdd")
req.Form = make(url.Values)
req.Form.Set("grant_type", string(PASSWORD))
req.Form.Set("username", "testing")
req.Form.Set("password", "testing")
req.Form.Set("state", "a")
req.PostForm = make(url.Values)
if ar := server.HandleAccessRequest(resp, req); ar != nil {
ar.Authorized = ar.Username == "testing" && ar.Password == "testing"
server.FinishAccessRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("Unexpected access token: %s", d)
}
if d := resp.Output["refresh_token"]; d != "r1" {
t.Fatalf("Unexpected refresh token: %s", d)
}
}
func TestAccessClientCredentials(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAccessTypes = AllowedAccessType{CLIENT_CREDENTIALS}
server := NewServer(sconfig, NewTestingStorage())
server.AccessTokenGen = &TestingAccessTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("1234", "aabbccdd")
req.Form = make(url.Values)
req.Form.Set("grant_type", string(CLIENT_CREDENTIALS))
req.Form.Set("state", "a")
req.PostForm = make(url.Values)
if ar := server.HandleAccessRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("Unexpected access token: %s", d)
}
if d, dok := resp.Output["refresh_token"]; dok {
t.Fatalf("Refresh token should not be generated: %s", d)
}
}
func TestExtraScopes(t *testing.T) {
if extraScopes("", "") == true {
t.Fatalf("extraScopes returned true with empty scopes")
}
if extraScopes("a", "") == true {
t.Fatalf("extraScopes returned true with less scopes")
}
if extraScopes("a,b", "b,a") == true {
t.Fatalf("extraScopes returned true with matching scopes")
}
if extraScopes("a,b", "b,a,c") == false {
t.Fatalf("extraScopes returned false with extra scopes")
}
if extraScopes("", "a") == false {
t.Fatalf("extraScopes returned false with extra scopes")
}
}
// clientWithoutMatcher just implements the base Client interface
type clientWithoutMatcher struct {
Id string
Secret string
RedirectUri string
}
func (c *clientWithoutMatcher) GetId() string { return c.Id }
func (c *clientWithoutMatcher) GetSecret() string { return c.Secret }
func (c *clientWithoutMatcher) GetRedirectUri() string { return c.RedirectUri }
func (c *clientWithoutMatcher) GetUserData() interface{} { return nil }
func TestGetClientWithoutMatcher(t *testing.T) {
myclient := &clientWithoutMatcher{
Id: "myclient",
Secret: "myclientsecret",
RedirectUri: "http://www.example.com",
}
storage := &TestingStorage{clients: map[string]Client{myclient.Id: myclient}}
// Ensure bad secret fails
{
auth := &BasicAuth{
Username: "myclient",
Password: "invalidsecret",
}
w := &Response{}
client := getClient(auth, storage, w)
if client != nil {
t.Errorf("Expected error, got client: %v", client)
}
if !w.IsError {
t.Error("No error in response")
}
if w.ErrorId != E_UNAUTHORIZED_CLIENT {
t.Errorf("Expected error %v, got %v", E_UNAUTHORIZED_CLIENT, w.ErrorId)
}
}
// Ensure nonexistent client fails
{
auth := &BasicAuth{
Username: "nonexistent",
Password: "nonexistent",
}
w := &Response{}
client := getClient(auth, storage, w)
if client != nil {
t.Errorf("Expected error, got client: %v", client)
}
if !w.IsError {
t.Error("No error in response")
}
if w.ErrorId != E_UNAUTHORIZED_CLIENT {
t.Errorf("Expected error %v, got %v", E_UNAUTHORIZED_CLIENT, w.ErrorId)
}
}
// Ensure good secret works
{
auth := &BasicAuth{
Username: "myclient",
Password: "myclientsecret",
}
w := &Response{}
client := getClient(auth, storage, w)
if client != myclient {
t.Errorf("Expected client, got nil with response: %v", w)
}
}
}
// clientWithMatcher implements the base Client interface and the ClientSecretMatcher interface
type clientWithMatcher struct {
Id string
Secret string
RedirectUri string
}
func (c *clientWithMatcher) GetId() string { return c.Id }
func (c *clientWithMatcher) GetSecret() string { panic("called GetSecret"); return "" }
func (c *clientWithMatcher) GetRedirectUri() string { return c.RedirectUri }
func (c *clientWithMatcher) GetUserData() interface{} { return nil }
func (c *clientWithMatcher) ClientSecretMatches(secret string) bool {
return secret == c.Secret
}
func TestGetClientSecretMatcher(t *testing.T) {
myclient := &clientWithMatcher{
Id: "myclient",
Secret: "myclientsecret",
RedirectUri: "http://www.example.com",
}
storage := &TestingStorage{clients: map[string]Client{myclient.Id: myclient}}
// Ensure bad secret fails, but does not panic (doesn't call GetSecret)
{
auth := &BasicAuth{
Username: "myclient",
Password: "invalidsecret",
}
w := &Response{}
client := getClient(auth, storage, w)
if client != nil {
t.Errorf("Expected error, got client: %v", client)
}
}
// Ensure good secret works, but does not panic (doesn't call GetSecret)
{
auth := &BasicAuth{
Username: "myclient",
Password: "myclientsecret",
}
w := &Response{}
client := getClient(auth, storage, w)
if client != myclient {
t.Errorf("Expected client, got nil with response: %v", w)
}
}
}
func TestAccessAuthorizationCodePKCE(t *testing.T) {
testcases := map[string]struct {
Challenge string
ChallengeMethod string
Verifier string
ExpectedError string
}{
"good, plain": {
Challenge: "12345678901234567890123456789012345678901234567890",
Verifier: "12345678901234567890123456789012345678901234567890",
},
"bad, plain": {
Challenge: "12345678901234567890123456789012345678901234567890",
Verifier: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
ExpectedError: "invalid_grant",
},
"good, S256": {
Challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
ChallengeMethod: "S256",
Verifier: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
},
"bad, S256": {
Challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
ChallengeMethod: "S256",
Verifier: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
ExpectedError: "invalid_grant",
},
"missing from storage": {
Challenge: "",
ChallengeMethod: "",
Verifier: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
},
}
for k, test := range testcases {
testStorage := NewTestingStorage()
sconfig := NewServerConfig()
sconfig.AllowedAccessTypes = AllowedAccessType{AUTHORIZATION_CODE}
server := NewServer(sconfig, testStorage)
server.AccessTokenGen = &TestingAccessTokenGen{}
server.Storage.SaveAuthorize(&AuthorizeData{
Client: testStorage.clients["public-client"],
Code: "pkce-code",
ExpiresIn: 3600,
CreatedAt: time.Now(),
RedirectUri: "http://localhost:14000/appauth",
CodeChallenge: test.Challenge,
CodeChallengeMethod: test.ChallengeMethod,
})
resp := server.NewResponse()
req, err := http.NewRequest("POST", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.SetBasicAuth("public-client", "")
req.Form = make(url.Values)
req.Form.Set("grant_type", string(AUTHORIZATION_CODE))
req.Form.Set("code", "pkce-code")
req.Form.Set("state", "a")
req.Form.Set("code_verifier", test.Verifier)
req.PostForm = make(url.Values)
if ar := server.HandleAccessRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, req, ar)
}
if resp.IsError {
if test.ExpectedError == "" || test.ExpectedError != resp.ErrorId {
t.Errorf("%s: unexpected error: %v, %v", k, resp.ErrorId, resp.StatusText)
continue
}
}
if test.ExpectedError == "" {
if resp.Type != DATA {
t.Fatalf("%s: Response should be data", k)
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("%s: Unexpected access token: %s", k, d)
}
if d := resp.Output["refresh_token"]; d != "r1" {
t.Fatalf("%s: Unexpected refresh token: %s", k, d)
}
}
}
}

275
vendor/github.com/RangelReale/osin/authorize.go generated vendored Normal file
View File

@@ -0,0 +1,275 @@
package osin
import (
"net/http"
"net/url"
"regexp"
"time"
)
// AuthorizeRequestType is the type for OAuth param `response_type`
type AuthorizeRequestType string
const (
CODE AuthorizeRequestType = "code"
TOKEN AuthorizeRequestType = "token"
PKCE_PLAIN = "plain"
PKCE_S256 = "S256"
)
var (
pkceMatcher = regexp.MustCompile("^[a-zA-Z0-9~._-]{43,128}$")
)
// Authorize request information
type AuthorizeRequest struct {
Type AuthorizeRequestType
Client Client
Scope string
RedirectUri string
State string
// Set if request is authorized
Authorized bool
// Token expiration in seconds. Change if different from default.
// If type = TOKEN, this expiration will be for the ACCESS token.
Expiration int32
// Data to be passed to storage. Not used by the library.
UserData interface{}
// HttpRequest *http.Request for special use
HttpRequest *http.Request
// Optional code_challenge as described in rfc7636
CodeChallenge string
// Optional code_challenge_method as described in rfc7636
CodeChallengeMethod string
}
// Authorization data
type AuthorizeData struct {
// Client information
Client Client
// Authorization code
Code string
// Token expiration in seconds
ExpiresIn int32
// Requested scope
Scope string
// Redirect Uri from request
RedirectUri string
// State data from request
State string
// Date created
CreatedAt time.Time
// Data to be passed to storage. Not used by the library.
UserData interface{}
// Optional code_challenge as described in rfc7636
CodeChallenge string
// Optional code_challenge_method as described in rfc7636
CodeChallengeMethod string
}
// IsExpired is true if authorization expired
func (d *AuthorizeData) IsExpired() bool {
return d.IsExpiredAt(time.Now())
}
// IsExpired is true if authorization expires at time 't'
func (d *AuthorizeData) IsExpiredAt(t time.Time) bool {
return d.ExpireAt().Before(t)
}
// ExpireAt returns the expiration date
func (d *AuthorizeData) ExpireAt() time.Time {
return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
}
// AuthorizeTokenGen is the token generator interface
type AuthorizeTokenGen interface {
GenerateAuthorizeToken(data *AuthorizeData) (string, error)
}
// HandleAuthorizeRequest is the main http.HandlerFunc for handling
// authorization requests
func (s *Server) HandleAuthorizeRequest(w *Response, r *http.Request) *AuthorizeRequest {
r.ParseForm()
// create the authorization request
unescapedUri, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
if err != nil {
w.SetErrorState(E_INVALID_REQUEST, "", "")
w.InternalError = err
return nil
}
ret := &AuthorizeRequest{
State: r.Form.Get("state"),
Scope: r.Form.Get("scope"),
RedirectUri: unescapedUri,
Authorized: false,
HttpRequest: r,
}
// must have a valid client
ret.Client, err = w.Storage.GetClient(r.Form.Get("client_id"))
if err == ErrNotFound {
w.SetErrorState(E_UNAUTHORIZED_CLIENT, "", ret.State)
return nil
}
if err != nil {
w.SetErrorState(E_SERVER_ERROR, "", ret.State)
w.InternalError = err
return nil
}
if ret.Client == nil {
w.SetErrorState(E_UNAUTHORIZED_CLIENT, "", ret.State)
return nil
}
if ret.Client.GetRedirectUri() == "" {
w.SetErrorState(E_UNAUTHORIZED_CLIENT, "", ret.State)
return nil
}
// check redirect uri, if there are multiple client redirect uri's
// don't set the uri
if ret.RedirectUri == "" && FirstUri(ret.Client.GetRedirectUri(), s.Config.RedirectUriSeparator) == ret.Client.GetRedirectUri() {
ret.RedirectUri = FirstUri(ret.Client.GetRedirectUri(), s.Config.RedirectUriSeparator)
}
if err = ValidateUriList(ret.Client.GetRedirectUri(), ret.RedirectUri, s.Config.RedirectUriSeparator); err != nil {
w.SetErrorState(E_INVALID_REQUEST, "", ret.State)
w.InternalError = err
return nil
}
w.SetRedirect(ret.RedirectUri)
requestType := AuthorizeRequestType(r.Form.Get("response_type"))
if s.Config.AllowedAuthorizeTypes.Exists(requestType) {
switch requestType {
case CODE:
ret.Type = CODE
ret.Expiration = s.Config.AuthorizationExpiration
// Optional PKCE support (https://tools.ietf.org/html/rfc7636)
if codeChallenge := r.Form.Get("code_challenge"); len(codeChallenge) == 0 {
if s.Config.RequirePKCEForPublicClients && CheckClientSecret(ret.Client, "") {
// https://tools.ietf.org/html/rfc7636#section-4.4.1
w.SetErrorState(E_INVALID_REQUEST, "code_challenge (rfc7636) required for public clients", ret.State)
return nil
}
} else {
codeChallengeMethod := r.Form.Get("code_challenge_method")
// allowed values are "plain" (default) and "S256", per https://tools.ietf.org/html/rfc7636#section-4.3
if len(codeChallengeMethod) == 0 {
codeChallengeMethod = PKCE_PLAIN
}
if codeChallengeMethod != PKCE_PLAIN && codeChallengeMethod != PKCE_S256 {
// https://tools.ietf.org/html/rfc7636#section-4.4.1
w.SetErrorState(E_INVALID_REQUEST, "code_challenge_method transform algorithm not supported (rfc7636)", ret.State)
return nil
}
// https://tools.ietf.org/html/rfc7636#section-4.2
if matched := pkceMatcher.MatchString(codeChallenge); !matched {
w.SetErrorState(E_INVALID_REQUEST, "code_challenge invalid (rfc7636)", ret.State)
return nil
}
ret.CodeChallenge = codeChallenge
ret.CodeChallengeMethod = codeChallengeMethod
}
case TOKEN:
ret.Type = TOKEN
ret.Expiration = s.Config.AccessExpiration
}
return ret
}
w.SetErrorState(E_UNSUPPORTED_RESPONSE_TYPE, "", ret.State)
return nil
}
func (s *Server) FinishAuthorizeRequest(w *Response, r *http.Request, ar *AuthorizeRequest) {
// don't process if is already an error
if w.IsError {
return
}
// force redirect response
w.SetRedirect(ar.RedirectUri)
if ar.Authorized {
if ar.Type == TOKEN {
w.SetRedirectFragment(true)
// generate token directly
ret := &AccessRequest{
Type: IMPLICIT,
Code: "",
Client: ar.Client,
RedirectUri: ar.RedirectUri,
Scope: ar.Scope,
GenerateRefresh: false, // per the RFC, should NOT generate a refresh token in this case
Authorized: true,
Expiration: ar.Expiration,
UserData: ar.UserData,
}
s.FinishAccessRequest(w, r, ret)
if ar.State != "" && w.InternalError == nil {
w.Output["state"] = ar.State
}
} else {
// generate authorization token
ret := &AuthorizeData{
Client: ar.Client,
CreatedAt: s.Now(),
ExpiresIn: ar.Expiration,
RedirectUri: ar.RedirectUri,
State: ar.State,
Scope: ar.Scope,
UserData: ar.UserData,
// Optional PKCE challenge
CodeChallenge: ar.CodeChallenge,
CodeChallengeMethod: ar.CodeChallengeMethod,
}
// generate token code
code, err := s.AuthorizeTokenGen.GenerateAuthorizeToken(ret)
if err != nil {
w.SetErrorState(E_SERVER_ERROR, "", ar.State)
w.InternalError = err
return
}
ret.Code = code
// save authorization token
if err = w.Storage.SaveAuthorize(ret); err != nil {
w.SetErrorState(E_SERVER_ERROR, "", ar.State)
w.InternalError = err
return
}
// redirect with code
w.Output["code"] = ret.Code
w.Output["state"] = ret.State
}
} else {
// redirect with error
w.SetErrorState(E_ACCESS_DENIED, "", ar.State)
}
}

296
vendor/github.com/RangelReale/osin/authorize_test.go generated vendored Normal file
View File

@@ -0,0 +1,296 @@
package osin
import (
"net/http"
"net/url"
"strings"
"testing"
)
func TestAuthorizeCode(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{CODE}
server := NewServer(sconfig, NewTestingStorage())
server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(CODE))
req.Form.Set("client_id", "1234")
req.Form.Set("state", "a")
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != REDIRECT {
t.Fatalf("Response should be a redirect")
}
if d := resp.Output["code"]; d != "1" {
t.Fatalf("Unexpected authorization code: %s", d)
}
}
func TestAuthorizeToken(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{TOKEN}
server := NewServer(sconfig, NewTestingStorage())
server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
server.AccessTokenGen = &TestingAccessTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(TOKEN))
req.Form.Set("client_id", "1234")
req.Form.Set("state", "a")
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != REDIRECT || !resp.RedirectInFragment {
t.Fatalf("Response should be a redirect with fragment")
}
if d := resp.Output["access_token"]; d != "1" {
t.Fatalf("Unexpected access token: %s", d)
}
}
func TestAuthorizeTokenWithInvalidClient(t *testing.T) {
sconfig := NewServerConfig()
sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{TOKEN}
server := NewServer(sconfig, NewTestingStorage())
server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
server.AccessTokenGen = &TestingAccessTokenGen{}
resp := server.NewResponse()
redirectUri := "http://redirecturi.com"
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(TOKEN))
req.Form.Set("client_id", "invalidclient")
req.Form.Set("state", "a")
req.Form.Set("redirect_uri", redirectUri)
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
if !resp.IsError {
t.Fatalf("Response should be an error")
}
if resp.ErrorId != E_UNAUTHORIZED_CLIENT {
t.Fatalf("Incorrect error in response: %v", resp.ErrorId)
}
usedRedirectUrl, redirectErr := resp.GetRedirectUrl()
if redirectErr == nil && usedRedirectUrl == redirectUri {
t.Fatalf("Response must not redirect to the provided redirect URL for an invalid client")
}
}
func TestAuthorizeCodePKCERequired(t *testing.T) {
sconfig := NewServerConfig()
sconfig.RequirePKCEForPublicClients = true
sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{CODE}
server := NewServer(sconfig, NewTestingStorage())
server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
// Public client returns an error
{
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(CODE))
req.Form.Set("state", "a")
req.Form.Set("client_id", "public-client")
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
if !resp.IsError || resp.ErrorId != "invalid_request" || strings.Contains(resp.StatusText, "code_challenge") {
t.Errorf("Expected invalid_request error describing the code_challenge required, got %#v", resp)
}
}
// Confidential client works without PKCE
{
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(CODE))
req.Form.Set("state", "a")
req.Form.Set("client_id", "1234")
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != REDIRECT {
t.Fatalf("Response should be a redirect")
}
if d := resp.Output["code"]; d != "1" {
t.Fatalf("Unexpected authorization code: %s", d)
}
}
}
func TestAuthorizeCodePKCEPlain(t *testing.T) {
challenge := "12345678901234567890123456789012345678901234567890"
sconfig := NewServerConfig()
sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{CODE}
server := NewServer(sconfig, NewTestingStorage())
server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(CODE))
req.Form.Set("client_id", "1234")
req.Form.Set("state", "a")
req.Form.Set("code_challenge", challenge)
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != REDIRECT {
t.Fatalf("Response should be a redirect")
}
code, ok := resp.Output["code"].(string)
if !ok || code != "1" {
t.Fatalf("Unexpected authorization code: %s", code)
}
token, err := server.Storage.LoadAuthorize(code)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if token.CodeChallenge != challenge {
t.Errorf("Expected stored code_challenge %s, got %s", challenge, token.CodeChallenge)
}
if token.CodeChallengeMethod != "plain" {
t.Errorf("Expected stored code_challenge plain, got %s", token.CodeChallengeMethod)
}
}
func TestAuthorizeCodePKCES256(t *testing.T) {
challenge := "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
sconfig := NewServerConfig()
sconfig.AllowedAuthorizeTypes = AllowedAuthorizeType{CODE}
server := NewServer(sconfig, NewTestingStorage())
server.AuthorizeTokenGen = &TestingAuthorizeTokenGen{}
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("response_type", string(CODE))
req.Form.Set("client_id", "1234")
req.Form.Set("state", "a")
req.Form.Set("code_challenge", challenge)
req.Form.Set("code_challenge_method", "S256")
if ar := server.HandleAuthorizeRequest(resp, req); ar != nil {
ar.Authorized = true
server.FinishAuthorizeRequest(resp, req, ar)
}
//fmt.Printf("%+v", resp)
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != REDIRECT {
t.Fatalf("Response should be a redirect")
}
code, ok := resp.Output["code"].(string)
if !ok || code != "1" {
t.Fatalf("Unexpected authorization code: %s", code)
}
token, err := server.Storage.LoadAuthorize(code)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if token.CodeChallenge != challenge {
t.Errorf("Expected stored code_challenge %s, got %s", challenge, token.CodeChallenge)
}
if token.CodeChallengeMethod != "S256" {
t.Errorf("Expected stored code_challenge S256, got %s", token.CodeChallengeMethod)
}
}

60
vendor/github.com/RangelReale/osin/client.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
package osin
// Client information
type Client interface {
// Client id
GetId() string
// Client secret
GetSecret() string
// Base client uri
GetRedirectUri() string
// Data to be passed to storage. Not used by the library.
GetUserData() interface{}
}
// ClientSecretMatcher is an optional interface clients can implement
// which allows them to be the one to determine if a secret matches.
// If a Client implements ClientSecretMatcher, the framework will never call GetSecret
type ClientSecretMatcher interface {
// SecretMatches returns true if the given secret matches
ClientSecretMatches(secret string) bool
}
// DefaultClient stores all data in struct variables
type DefaultClient struct {
Id string
Secret string
RedirectUri string
UserData interface{}
}
func (d *DefaultClient) GetId() string {
return d.Id
}
func (d *DefaultClient) GetSecret() string {
return d.Secret
}
func (d *DefaultClient) GetRedirectUri() string {
return d.RedirectUri
}
func (d *DefaultClient) GetUserData() interface{} {
return d.UserData
}
// Implement the ClientSecretMatcher interface
func (d *DefaultClient) ClientSecretMatches(secret string) bool {
return d.Secret == secret
}
func (d *DefaultClient) CopyFrom(client Client) {
d.Id = client.GetId()
d.Secret = client.GetSecret()
d.RedirectUri = client.GetRedirectUri()
d.UserData = client.GetUserData()
}

18
vendor/github.com/RangelReale/osin/client_test.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
package osin
import (
"testing"
)
func TestClientIntfUserData(t *testing.T) {
c := &DefaultClient{
UserData: make(map[string]interface{}),
}
// check if the interface{} returned from the method is a reference
c.GetUserData().(map[string]interface{})["test"] = "none"
if _, ok := c.GetUserData().(map[string]interface{})["test"]; !ok {
t.Error("Returned interface is not a reference")
}
}

82
vendor/github.com/RangelReale/osin/config.go generated vendored Normal file
View File

@@ -0,0 +1,82 @@
package osin
// AllowedAuthorizeType is a collection of allowed auth request types
type AllowedAuthorizeType []AuthorizeRequestType
// Exists returns true if the auth type exists in the list
func (t AllowedAuthorizeType) Exists(rt AuthorizeRequestType) bool {
for _, k := range t {
if k == rt {
return true
}
}
return false
}
// AllowedAccessType is a collection of allowed access request types
type AllowedAccessType []AccessRequestType
// Exists returns true if the access type exists in the list
func (t AllowedAccessType) Exists(rt AccessRequestType) bool {
for _, k := range t {
if k == rt {
return true
}
}
return false
}
// ServerConfig contains server configuration information
type ServerConfig struct {
// Authorization token expiration in seconds (default 5 minutes)
AuthorizationExpiration int32
// Access token expiration in seconds (default 1 hour)
AccessExpiration int32
// Token type to return
TokenType string
// List of allowed authorize types (only CODE by default)
AllowedAuthorizeTypes AllowedAuthorizeType
// List of allowed access types (only AUTHORIZATION_CODE by default)
AllowedAccessTypes AllowedAccessType
// HTTP status code to return for errors - default 200
// Only used if response was created from server
ErrorStatusCode int
// If true allows client secret also in params, else only in
// Authorization header - default false
AllowClientSecretInParams bool
// If true allows access request using GET, else only POST - default false
AllowGetAccessRequest bool
// Require PKCE for code flows for public OAuth clients - default false
RequirePKCEForPublicClients bool
// Separator to support multiple URIs in Client.GetRedirectUri().
// If blank (the default), don't allow multiple URIs.
RedirectUriSeparator string
// RetainTokenAfter Refresh allows the server to retain the access and
// refresh token for re-use - default false
RetainTokenAfterRefresh bool
}
// NewServerConfig returns a new ServerConfig with default configuration
func NewServerConfig() *ServerConfig {
return &ServerConfig{
AuthorizationExpiration: 250,
AccessExpiration: 3600,
TokenType: "Bearer",
AllowedAuthorizeTypes: AllowedAuthorizeType{CODE},
AllowedAccessTypes: AllowedAccessType{AUTHORIZATION_CODE},
ErrorStatusCode: 200,
AllowClientSecretInParams: false,
AllowGetAccessRequest: false,
RetainTokenAfterRefresh: false,
}
}

52
vendor/github.com/RangelReale/osin/error.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package osin
type DefaultErrorId string
const (
E_INVALID_REQUEST string = "invalid_request"
E_UNAUTHORIZED_CLIENT = "unauthorized_client"
E_ACCESS_DENIED = "access_denied"
E_UNSUPPORTED_RESPONSE_TYPE = "unsupported_response_type"
E_INVALID_SCOPE = "invalid_scope"
E_SERVER_ERROR = "server_error"
E_TEMPORARILY_UNAVAILABLE = "temporarily_unavailable"
E_UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"
E_INVALID_GRANT = "invalid_grant"
E_INVALID_CLIENT = "invalid_client"
)
var (
deferror *DefaultErrors = NewDefaultErrors()
)
// Default errors and messages
type DefaultErrors struct {
errormap map[string]string
}
// NewDefaultErrors initializes OAuth2 error codes and descriptions.
// http://tools.ietf.org/html/rfc6749#section-4.1.2.1
// http://tools.ietf.org/html/rfc6749#section-4.2.2.1
// http://tools.ietf.org/html/rfc6749#section-5.2
// http://tools.ietf.org/html/rfc6749#section-7.2
func NewDefaultErrors() *DefaultErrors {
r := &DefaultErrors{errormap: make(map[string]string)}
r.errormap[E_INVALID_REQUEST] = "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed."
r.errormap[E_UNAUTHORIZED_CLIENT] = "The client is not authorized to request a token using this method."
r.errormap[E_ACCESS_DENIED] = "The resource owner or authorization server denied the request."
r.errormap[E_UNSUPPORTED_RESPONSE_TYPE] = "The authorization server does not support obtaining a token using this method."
r.errormap[E_INVALID_SCOPE] = "The requested scope is invalid, unknown, or malformed."
r.errormap[E_SERVER_ERROR] = "The authorization server encountered an unexpected condition that prevented it from fulfilling the request."
r.errormap[E_TEMPORARILY_UNAVAILABLE] = "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server."
r.errormap[E_UNSUPPORTED_GRANT_TYPE] = "The authorization grant type is not supported by the authorization server."
r.errormap[E_INVALID_GRANT] = "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
r.errormap[E_INVALID_CLIENT] = "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."
return r
}
func (e *DefaultErrors) Get(id string) string {
if m, ok := e.errormap[id]; ok {
return m
}
return id
}

View File

@@ -0,0 +1,411 @@
package main
// Open url in browser:
// http://localhost:14000/app
import (
"fmt"
"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
"net/http"
"net/url"
)
func main() {
sconfig := osin.NewServerConfig()
sconfig.AllowedAuthorizeTypes = osin.AllowedAuthorizeType{osin.CODE, osin.TOKEN}
sconfig.AllowedAccessTypes = osin.AllowedAccessType{osin.AUTHORIZATION_CODE,
osin.REFRESH_TOKEN, osin.PASSWORD, osin.CLIENT_CREDENTIALS, osin.ASSERTION}
sconfig.AllowGetAccessRequest = true
sconfig.AllowClientSecretInParams = true
server := osin.NewServer(sconfig, example.NewTestStorage())
// Authorization code endpoint
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !example.HandleLoginPage(ar, w, r) {
return
}
ar.UserData = struct{ Login string }{Login: "test"}
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
if !resp.IsError {
resp.Output["custom_parameter"] = 187723
}
osin.OutputJSON(resp, w, r)
})
// Access token endpoint
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
switch ar.Type {
case osin.AUTHORIZATION_CODE:
ar.Authorized = true
case osin.REFRESH_TOKEN:
ar.Authorized = true
case osin.PASSWORD:
if ar.Username == "test" && ar.Password == "test" {
ar.Authorized = true
}
case osin.CLIENT_CREDENTIALS:
ar.Authorized = true
case osin.ASSERTION:
if ar.AssertionType == "urn:osin.example.complete" && ar.Assertion == "osin.data" {
ar.Authorized = true
}
}
server.FinishAccessRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
if !resp.IsError {
resp.Output["custom_parameter"] = 19923
}
osin.OutputJSON(resp, w, r)
})
// Information endpoint
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ir := server.HandleInfoRequest(resp, r); ir != nil {
server.FinishInfoRequest(resp, r, ir)
}
osin.OutputJSON(resp, w, r)
})
// Application home endpoint
http.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html><body>"))
w.Write([]byte(fmt.Sprintf("<a href=\"/authorize?response_type=code&client_id=1234&state=xyz&scope=everything&redirect_uri=%s\">Code</a><br/>", url.QueryEscape("http://localhost:14000/appauth/code"))))
w.Write([]byte(fmt.Sprintf("<a href=\"/authorize?response_type=token&client_id=1234&state=xyz&scope=everything&redirect_uri=%s\">Implicit</a><br/>", url.QueryEscape("http://localhost:14000/appauth/token"))))
w.Write([]byte(fmt.Sprintf("<a href=\"/appauth/password\">Password</a><br/>")))
w.Write([]byte(fmt.Sprintf("<a href=\"/appauth/client_credentials\">Client Credentials</a><br/>")))
w.Write([]byte(fmt.Sprintf("<a href=\"/appauth/assertion\">Assertion</a><br/>")))
w.Write([]byte("</body></html>"))
})
// Application destination - CODE
http.HandleFunc("/appauth/code", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
code := r.Form.Get("code")
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - CODE<br/>"))
defer w.Write([]byte("</body></html>"))
if code == "" {
w.Write([]byte("Nothing to do"))
return
}
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=authorization_code&client_id=1234&client_secret=aabbccdd&state=xyz&redirect_uri=%s&code=%s",
url.QueryEscape("http://localhost:14000/appauth/code"), url.QueryEscape(code))
// if parse, download and parse json
if r.Form.Get("doparse") == "1" {
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{"1234", "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
// output links
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Goto Token URL</a><br/>", aurl)))
cururl := *r.URL
curq := cururl.Query()
curq.Add("doparse", "1")
cururl.RawQuery = curq.Encode()
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Download Token</a><br/>", cururl.String())))
if rt, ok := jr["refresh_token"]; ok {
rurl := fmt.Sprintf("/appauth/refresh?code=%s", rt)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Refresh Token</a><br/>", rurl)))
}
if at, ok := jr["access_token"]; ok {
rurl := fmt.Sprintf("/appauth/info?code=%s", at)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Info</a><br/>", rurl)))
}
})
// Application destination - TOKEN
http.HandleFunc("/appauth/token", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - TOKEN<br/>"))
w.Write([]byte("Response data in fragment - not acessible via server - Nothing to do"))
w.Write([]byte("</body></html>"))
})
// Application destination - PASSWORD
http.HandleFunc("/appauth/password", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - PASSWORD<br/>"))
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=password&scope=everything&username=%s&password=%s",
"test", "test")
// download token
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{Username: "1234", Password: "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
if rt, ok := jr["refresh_token"]; ok {
rurl := fmt.Sprintf("/appauth/refresh?code=%s", rt)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Refresh Token</a><br/>", rurl)))
}
if at, ok := jr["access_token"]; ok {
rurl := fmt.Sprintf("/appauth/info?code=%s", at)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Info</a><br/>", rurl)))
}
w.Write([]byte("</body></html>"))
})
// Application destination - CLIENT_CREDENTIALS
http.HandleFunc("/appauth/client_credentials", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - CLIENT CREDENTIALS<br/>"))
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=client_credentials")
// download token
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{Username: "1234", Password: "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
if rt, ok := jr["refresh_token"]; ok {
rurl := fmt.Sprintf("/appauth/refresh?code=%s", rt)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Refresh Token</a><br/>", rurl)))
}
if at, ok := jr["access_token"]; ok {
rurl := fmt.Sprintf("/appauth/info?code=%s", at)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Info</a><br/>", rurl)))
}
w.Write([]byte("</body></html>"))
})
// Application destination - ASSERTION
http.HandleFunc("/appauth/assertion", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - ASSERTION<br/>"))
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=assertion&assertion_type=urn:osin.example.complete&assertion=osin.data")
// download token
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{Username: "1234", Password: "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
if rt, ok := jr["refresh_token"]; ok {
rurl := fmt.Sprintf("/appauth/refresh?code=%s", rt)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Refresh Token</a><br/>", rurl)))
}
if at, ok := jr["access_token"]; ok {
rurl := fmt.Sprintf("/appauth/info?code=%s", at)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Info</a><br/>", rurl)))
}
w.Write([]byte("</body></html>"))
})
// Application destination - REFRESH
http.HandleFunc("/appauth/refresh", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - REFRESH<br/>"))
defer w.Write([]byte("</body></html>"))
code := r.Form.Get("code")
if code == "" {
w.Write([]byte("Nothing to do"))
return
}
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=refresh_token&refresh_token=%s", url.QueryEscape(code))
// download token
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{Username: "1234", Password: "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
if rt, ok := jr["refresh_token"]; ok {
rurl := fmt.Sprintf("/appauth/refresh?code=%s", rt)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Refresh Token</a><br/>", rurl)))
}
if at, ok := jr["access_token"]; ok {
rurl := fmt.Sprintf("/appauth/info?code=%s", at)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Info</a><br/>", rurl)))
}
})
// Application destination - INFO
http.HandleFunc("/appauth/info", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - INFO<br/>"))
defer w.Write([]byte("</body></html>"))
code := r.Form.Get("code")
if code == "" {
w.Write([]byte("Nothing to do"))
return
}
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/info?code=%s", url.QueryEscape(code))
// download token
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{Username: "1234", Password: "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
if rt, ok := jr["refresh_token"]; ok {
rurl := fmt.Sprintf("/appauth/refresh?code=%s", rt)
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Refresh Token</a><br/>", rurl)))
}
})
http.ListenAndServe(":14000", nil)
}

View File

@@ -0,0 +1,130 @@
package main
// Use golang.org/x/oauth2 client to test
// Open url in browser:
// http://localhost:14000/app
import (
"fmt"
"net/http"
"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
"golang.org/x/oauth2"
)
func main() {
config := osin.NewServerConfig()
// goauth2 checks errors using status codes
config.ErrorStatusCode = 401
server := osin.NewServer(config, example.NewTestStorage())
client := &oauth2.Config{
ClientID: "1234",
ClientSecret: "aabbccdd",
Endpoint: oauth2.Endpoint{
AuthURL: "http://localhost:14000/authorize",
TokenURL: "http://localhost:14000/token",
},
RedirectURL: "http://localhost:14000/appauth/code",
}
// Authorization code endpoint
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !example.HandleLoginPage(ar, w, r) {
return
}
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Access token endpoint
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Information endpoint
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ir := server.HandleInfoRequest(resp, r); ir != nil {
server.FinishInfoRequest(resp, r, ir)
}
osin.OutputJSON(resp, w, r)
})
// Application home endpoint
http.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html><body>"))
//w.Write([]byte(fmt.Sprintf("<a href=\"/authorize?response_type=code&client_id=1234&state=xyz&scope=everything&redirect_uri=%s\">Login</a><br/>", url.QueryEscape("http://localhost:14000/appauth/code"))))
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Login</a><br/>", client.AuthCodeURL(""))))
w.Write([]byte("</body></html>"))
})
// Application destination - CODE
http.HandleFunc("/appauth/code", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
code := r.Form.Get("code")
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - CODE<br/>"))
defer w.Write([]byte("</body></html>"))
if code == "" {
w.Write([]byte("Nothing to do"))
return
}
var jr *oauth2.Token
var err error
// if parse, download and parse json
if r.Form.Get("doparse") == "1" {
jr, err = client.Exchange(oauth2.NoContext, code)
if err != nil {
jr = nil
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", err)))
}
}
// show json access token
if jr != nil {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", jr.AccessToken)))
if jr.RefreshToken != "" {
w.Write([]byte(fmt.Sprintf("REFRESH TOKEN: %s<br/>\n", jr.RefreshToken)))
}
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
cururl := *r.URL
curq := cururl.Query()
curq.Add("doparse", "1")
cururl.RawQuery = curq.Encode()
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Download Token</a><br/>", cururl.String())))
})
http.ListenAndServe(":14000", nil)
}

57
vendor/github.com/RangelReale/osin/example/helper.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package example
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/RangelReale/osin"
)
func HandleLoginPage(ar *osin.AuthorizeRequest, w http.ResponseWriter, r *http.Request) bool {
r.ParseForm()
if r.Method == "POST" && r.Form.Get("login") == "test" && r.Form.Get("password") == "test" {
return true
}
w.Write([]byte("<html><body>"))
w.Write([]byte(fmt.Sprintf("LOGIN %s (use test/test)<br/>", ar.Client.GetId())))
w.Write([]byte(fmt.Sprintf("<form action=\"/authorize?%s\" method=\"POST\">", r.URL.RawQuery)))
w.Write([]byte("Login: <input type=\"text\" name=\"login\" /><br/>"))
w.Write([]byte("Password: <input type=\"password\" name=\"password\" /><br/>"))
w.Write([]byte("<input type=\"submit\"/>"))
w.Write([]byte("</form>"))
w.Write([]byte("</body></html>"))
return false
}
func DownloadAccessToken(url string, auth *osin.BasicAuth, output map[string]interface{}) error {
// download access token
preq, err := http.NewRequest("POST", url, nil)
if err != nil {
return err
}
if auth != nil {
preq.SetBasicAuth(auth.Username, auth.Password)
}
pclient := &http.Client{}
presp, err := pclient.Do(preq)
if err != nil {
return err
}
if presp.StatusCode != 200 {
return errors.New("Invalid status code")
}
jdec := json.NewDecoder(presp.Body)
err = jdec.Decode(&output)
return err
}

View File

@@ -0,0 +1,215 @@
package main
// Open url in browser:
// http://localhost:14000/app
import (
"crypto/rsa"
"fmt"
"net/http"
"net/url"
"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
jwt "github.com/dgrijalva/jwt-go"
)
// JWT access token generator
type AccessTokenGenJWT struct {
PrivateKey *rsa.PrivateKey
PublicKey *rsa.PublicKey
}
func (c *AccessTokenGenJWT) GenerateAccessToken(data *osin.AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) {
// generate JWT access token
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
"cid": data.Client.GetId(),
"exp": data.ExpireAt().Unix(),
})
accesstoken, err = token.SignedString(c.PrivateKey)
if err != nil {
return "", "", err
}
if !generaterefresh {
return
}
// generate JWT refresh token
token = jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
"cid": data.Client.GetId(),
})
refreshtoken, err = token.SignedString(c.PrivateKey)
if err != nil {
return "", "", err
}
return
}
func main() {
server := osin.NewServer(osin.NewServerConfig(), example.NewTestStorage())
var err error
var accessTokenGenJWT AccessTokenGenJWT
if accessTokenGenJWT.PrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privatekeyPEM); err != nil {
fmt.Printf("ERROR: %s\n", err)
return
}
if accessTokenGenJWT.PublicKey, err = jwt.ParseRSAPublicKeyFromPEM(publickeyPEM); err != nil {
fmt.Printf("ERROR: %s\n", err)
return
}
server.AccessTokenGen = &accessTokenGenJWT
// Authorization code endpoint
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !example.HandleLoginPage(ar, w, r) {
return
}
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Access token endpoint
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Information endpoint
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ir := server.HandleInfoRequest(resp, r); ir != nil {
server.FinishInfoRequest(resp, r, ir)
}
osin.OutputJSON(resp, w, r)
})
// Application home endpoint
http.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html><body>"))
w.Write([]byte(fmt.Sprintf("<a href=\"/authorize?response_type=code&client_id=1234&state=xyz&scope=everything&redirect_uri=%s\">Login</a><br/>", url.QueryEscape("http://localhost:14000/appauth/code"))))
w.Write([]byte("</body></html>"))
})
// Application destination - CODE
http.HandleFunc("/appauth/code", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
code := r.Form.Get("code")
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - CODE<br/>"))
defer w.Write([]byte("</body></html>"))
if code == "" {
w.Write([]byte("Nothing to do"))
return
}
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=authorization_code&client_id=1234&state=xyz&redirect_uri=%s&code=%s",
url.QueryEscape("http://localhost:14000/appauth/code"), url.QueryEscape(code))
// if parse, download and parse json
if r.Form.Get("doparse") == "1" {
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{"1234", "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
// output links
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Goto Token URL</a><br/>", aurl)))
cururl := *r.URL
curq := cururl.Query()
curq.Add("doparse", "1")
cururl.RawQuery = curq.Encode()
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Download Token</a><br/>", cururl.String())))
})
http.ListenAndServe(":14000", nil)
}
var (
privatekeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtn
SgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0i
cqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhC
PUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR+1DcKJzQBSTAGnpYVaqpsAR
ap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKA
Rdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7+2oZ2d3
n6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk+O+zDVTV2JszdcOt7E5dAy
MaomETAhRxB7hlIOnEN7WKm+dGNrKRvV0wDU5ReFMRHg31/Lnu8c+5BvGjZX+ky9
POIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr+ah1bjEXvlxDOWkHN6YfpV5ThdE
KdBIPGEVqa63r9n2h+qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gM
IvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDn
FcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvY
mEOjrWfZhF083/nYWE2kVquj2wjs+otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghj
FuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO+U
I5+XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCs
2b+chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNn
/iu37Y3NfemZBJA7hNl4dYe+f+uzM87cdQ214+jrAoGAXA0XxX8ll2+ToOLJsaNT
OvNB9h9Uc5qK5X5w+7G7O998BN2PC/MWp8H+2fVqpXgNENpNXttkRm1hk1dych86
EunfdPuqsX+as44oCyJGFHVBnWpm33eWQw9YqANRI+pCJzP08I5WK3osnPiwshd+
hR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1+s34Ycr8SvxsOBWxymG5zaCsUbPsL0
4aCgLScCHb9J+E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0Kcnckb
mDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC+WQ6drO2ThrypLVZQ+ry
eBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3
CKuHRG+AP579dncdUnOMvfXOtkdM4vk0+hWASBQzM9xzVcztCa+koAugjVaLS9A+
9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbq
-----END RSA PRIVATE KEY-----`)
publickeyPEM = []byte(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4f5wg5l2hKsTeNem/V41
fGnJm6gOdrj8ym3rFkEU/wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7
mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBp
HssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2
XrHhR+1DcKJzQBSTAGnpYVaqpsARap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3b
ODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy
7wIDAQAB
-----END PUBLIC KEY-----`)
)

View File

@@ -0,0 +1,273 @@
/*
An example of adding OpenID Connect support to osin.
*/
package main
import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
"gopkg.in/square/go-jose.v1"
)
var (
issuer = "http://127.0.0.1:14001"
server = osin.NewServer(osin.NewServerConfig(), example.NewTestStorage())
jwtSigner jose.Signer
publicKeys *jose.JsonWebKeySet
)
func main() {
// Load signing key.
block, _ := pem.Decode(privateKeyBytes)
if block == nil {
log.Fatalf("no private key found")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalf("failed to parse key: %v", err)
}
// Configure jwtSigner and public keys.
privateKey := &jose.JsonWebKey{
Key: key,
Algorithm: "RS256",
Use: "sig",
KeyID: "1", // KeyID should use the key thumbprint.
}
jwtSigner, err = jose.NewSigner(jose.RS256, privateKey)
if err != nil {
log.Fatalf("failed to create jwtSigner: %v", err)
}
publicKeys = &jose.JsonWebKeySet{
Keys: []jose.JsonWebKey{
jose.JsonWebKey{Key: &key.PublicKey,
Algorithm: "RS256",
Use: "sig",
KeyID: "1",
},
},
}
// Register the four manditory OpenID Connect endpoints: discovery, public keys, auth, and token.
http.HandleFunc("/.well-known/openid-configuration", handleDiscovery)
http.HandleFunc("/publickeys", handlePublicKeys)
http.HandleFunc("/authorize", handleAuthorization)
http.HandleFunc("/token", handleToken)
log.Fatal(http.ListenAndServe("127.0.0.1:14001", nil))
}
// The ID Token represents a JWT passed to the client as part of the token response.
//
// https://openid.net/specs/openid-connect-core-1_0.html#IDToken
type IDToken struct {
Issuer string `json:"iss"`
UserID string `json:"sub"`
ClientID string `json:"aud"`
Expiration int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
Nonce string `json:"nonce,omitempty"` // Non-manditory fields MUST be "omitempty"
// Custom claims supported by this server.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
Email string `json:"email,omitempty"`
EmailVerified *bool `json:"email_verified,omitempty"`
Name string `json:"name,omitempty"`
FamilyName string `json:"family_name,omitempty"`
GivenName string `json:"given_name,omitempty"`
Locale string `json:"locale,omitempty"`
}
// handleDiscovery returns the OpenID Connect discovery object, allowing clients
// to discover OAuth2 resources.
func handleDiscovery(w http.ResponseWriter, r *http.Request) {
// For other example see: https://accounts.google.com/.well-known/openid-configuration
data := map[string]interface{}{
"issuer": issuer,
"authorization_endpoint": issuer + "/authorize",
"token_endpoint": issuer + "/token",
"jwks_uri": issuer + "/publickeys",
"response_types_supported": []string{"code"},
"subject_types_supported": []string{"public"},
"id_token_signing_alg_values_supported": []string{"RS256"},
"scopes_supported": []string{"openid", "email", "profile"},
"token_endpoint_auth_methods_supported": []string{"client_secret_basic"},
"claims_supported": []string{
"aud", "email", "email_verified", "exp",
"family_name", "given_name", "iat", "iss",
"locale", "name", "sub",
},
}
raw, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Printf("failed to marshal data: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(raw)))
w.Write(raw)
}
// handlePublicKeys publishes the public part of this server's signing keys.
// This allows clients to verify the signature of ID Tokens.
func handlePublicKeys(w http.ResponseWriter, r *http.Request) {
raw, err := json.MarshalIndent(publicKeys, "", " ")
if err != nil {
log.Printf("failed to marshal data: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(raw)))
w.Write(raw)
}
func handleAuthorization(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !example.HandleLoginPage(ar, w, r) {
return
}
ar.Authorized = true
scopes := make(map[string]bool)
for _, s := range strings.Fields(ar.Scope) {
scopes[s] = true
}
// If the "openid" connect scope is specified, attach an ID Token to the
// authorization response.
//
// The ID Token will be serialized and signed during the code for token exchange.
if scopes["openid"] {
// These values would be tied to the end user authorizing the client.
now := time.Now()
idToken := IDToken{
Issuer: issuer,
UserID: "id-of-test-user",
ClientID: ar.Client.GetId(),
Expiration: now.Add(time.Hour).Unix(),
IssuedAt: now.Unix(),
Nonce: r.URL.Query().Get("nonce"),
}
if scopes["profile"] {
idToken.Name = "Jane Doe"
idToken.GivenName = "Jane"
idToken.FamilyName = "Doe"
idToken.Locale = "us"
}
if scopes["email"] {
t := true
idToken.Email = "jane.doe@example.com"
idToken.EmailVerified = &t
}
// NOTE: The storage must be able to encode and decode this object.
ar.UserData = &idToken
}
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
log.Printf("internal error: %v", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
}
func handleToken(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
// If an ID Token was encoded as the UserData, serialize and sign it.
if idToken, ok := ar.UserData.(*IDToken); ok && idToken != nil {
encodeIDToken(resp, idToken, jwtSigner)
}
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
}
// encodeIDToken serializes and signs an ID Token then adds a field to the token response.
func encodeIDToken(resp *osin.Response, idToken *IDToken, singer jose.Signer) {
resp.InternalError = func() error {
payload, err := json.Marshal(idToken)
if err != nil {
return fmt.Errorf("failed to marshal token: %v", err)
}
jws, err := jwtSigner.Sign(payload)
if err != nil {
return fmt.Errorf("failed to sign token: %v", err)
}
raw, err := jws.CompactSerialize()
if err != nil {
return fmt.Errorf("failed to serialize token: %v", err)
}
resp.Output["id_token"] = raw
return nil
}()
// Record errors as internal server errors.
if resp.InternalError != nil {
resp.IsError = true
resp.ErrorId = osin.E_SERVER_ERROR
}
}
var (
privateKeyBytes = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtn
SgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0i
cqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhC
PUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR+1DcKJzQBSTAGnpYVaqpsAR
ap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKA
Rdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7+2oZ2d3
n6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk+O+zDVTV2JszdcOt7E5dAy
MaomETAhRxB7hlIOnEN7WKm+dGNrKRvV0wDU5ReFMRHg31/Lnu8c+5BvGjZX+ky9
POIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr+ah1bjEXvlxDOWkHN6YfpV5ThdE
KdBIPGEVqa63r9n2h+qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gM
IvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDn
FcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvY
mEOjrWfZhF083/nYWE2kVquj2wjs+otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghj
FuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO+U
I5+XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCs
2b+chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNn
/iu37Y3NfemZBJA7hNl4dYe+f+uzM87cdQ214+jrAoGAXA0XxX8ll2+ToOLJsaNT
OvNB9h9Uc5qK5X5w+7G7O998BN2PC/MWp8H+2fVqpXgNENpNXttkRm1hk1dych86
EunfdPuqsX+as44oCyJGFHVBnWpm33eWQw9YqANRI+pCJzP08I5WK3osnPiwshd+
hR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1+s34Ycr8SvxsOBWxymG5zaCsUbPsL0
4aCgLScCHb9J+E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0Kcnckb
mDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC+WQ6drO2ThrypLVZQ+ry
eBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3
CKuHRG+AP579dncdUnOMvfXOtkdM4vk0+hWASBQzM9xzVcztCa+koAugjVaLS9A+
9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbq
-----END RSA PRIVATE KEY-----`)
)

View File

@@ -0,0 +1,127 @@
package main
// Use github.com/RangelReale/osincli client to test
// Open url in browser:
// http://localhost:14001
import (
"fmt"
"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
"github.com/RangelReale/osincli"
"net/http"
)
func main() {
// create http muxes
serverhttp := http.NewServeMux()
clienthttp := http.NewServeMux()
// create server
config := osin.NewServerConfig()
sstorage := example.NewTestStorage()
sstorage.SetClient("1234", &osin.DefaultClient{
Id: "1234",
Secret: "aabbccdd",
RedirectUri: "http://localhost:14001/appauth",
})
server := osin.NewServer(config, sstorage)
// create client
cliconfig := &osincli.ClientConfig{
ClientId: "1234",
ClientSecret: "aabbccdd",
AuthorizeUrl: "http://localhost:14000/authorize",
TokenUrl: "http://localhost:14000/token",
RedirectUrl: "http://localhost:14001/appauth",
}
client, err := osincli.NewClient(cliconfig)
if err != nil {
panic(err)
}
// create a new request to generate the url
areq := client.NewAuthorizeRequest(osincli.CODE)
// SERVER
// Authorization code endpoint
serverhttp.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !example.HandleLoginPage(ar, w, r) {
return
}
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Access token endpoint
serverhttp.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Information endpoint
serverhttp.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ir := server.HandleInfoRequest(resp, r); ir != nil {
server.FinishInfoRequest(resp, r, ir)
}
osin.OutputJSON(resp, w, r)
})
// CLIENT
// Home
clienthttp.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
u := areq.GetAuthorizeUrl()
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Login</a>", u.String())))
})
// Auth endpoint
clienthttp.HandleFunc("/appauth", func(w http.ResponseWriter, r *http.Request) {
// parse a token request
areqdata, err := areq.HandleRequest(r)
if err != nil {
w.Write([]byte(fmt.Sprintf("ERROR: %s\n", err)))
return
}
treq := client.NewAccessRequest(osincli.AUTHORIZATION_CODE, areqdata)
// show access request url (for debugging only)
u2 := treq.GetTokenUrl()
w.Write([]byte(fmt.Sprintf("Access token URL: %s\n", u2.String())))
// exchange the authorize token for the access token
ad, err := treq.GetToken()
if err != nil {
w.Write([]byte(fmt.Sprintf("ERROR: %s\n", err)))
return
}
w.Write([]byte(fmt.Sprintf("Access token: %+v\n", ad)))
})
go http.ListenAndServe(":14001", clienthttp)
http.ListenAndServe(":14000", serverhttp)
}

View File

@@ -0,0 +1,126 @@
package main
// Open url in browser:
// http://localhost:14000/app
import (
"fmt"
"github.com/RangelReale/osin"
"github.com/RangelReale/osin/example"
"net/http"
"net/url"
)
func main() {
cfg := osin.NewServerConfig()
cfg.AllowGetAccessRequest = true
cfg.AllowClientSecretInParams = true
server := osin.NewServer(cfg, example.NewTestStorage())
// Authorization code endpoint
http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {
if !example.HandleLoginPage(ar, w, r) {
return
}
ar.Authorized = true
server.FinishAuthorizeRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Access token endpoint
http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ar := server.HandleAccessRequest(resp, r); ar != nil {
ar.Authorized = true
server.FinishAccessRequest(resp, r, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
osin.OutputJSON(resp, w, r)
})
// Information endpoint
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
resp := server.NewResponse()
defer resp.Close()
if ir := server.HandleInfoRequest(resp, r); ir != nil {
server.FinishInfoRequest(resp, r, ir)
}
osin.OutputJSON(resp, w, r)
})
// Application home endpoint
http.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html><body>"))
w.Write([]byte(fmt.Sprintf("<a href=\"/authorize?response_type=code&client_id=1234&state=xyz&scope=everything&redirect_uri=%s\">Login</a><br/>", url.QueryEscape("http://localhost:14000/appauth/code"))))
w.Write([]byte("</body></html>"))
})
// Application destination - CODE
http.HandleFunc("/appauth/code", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
code := r.Form.Get("code")
w.Write([]byte("<html><body>"))
w.Write([]byte("APP AUTH - CODE<br/>"))
defer w.Write([]byte("</body></html>"))
if code == "" {
w.Write([]byte("Nothing to do"))
return
}
jr := make(map[string]interface{})
// build access code url
aurl := fmt.Sprintf("/token?grant_type=authorization_code&client_id=1234&client_secret=aabbccdd&state=xyz&redirect_uri=%s&code=%s",
url.QueryEscape("http://localhost:14000/appauth/code"), url.QueryEscape(code))
// if parse, download and parse json
if r.Form.Get("doparse") == "1" {
err := example.DownloadAccessToken(fmt.Sprintf("http://localhost:14000%s", aurl),
&osin.BasicAuth{"1234", "aabbccdd"}, jr)
if err != nil {
w.Write([]byte(err.Error()))
w.Write([]byte("<br/>"))
}
}
// show json error
if erd, ok := jr["error"]; ok {
w.Write([]byte(fmt.Sprintf("ERROR: %s<br/>\n", erd)))
}
// show json access token
if at, ok := jr["access_token"]; ok {
w.Write([]byte(fmt.Sprintf("ACCESS TOKEN: %s<br/>\n", at)))
}
w.Write([]byte(fmt.Sprintf("FULL RESULT: %+v<br/>\n", jr)))
// output links
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Goto Token URL</a><br/>", aurl)))
cururl := *r.URL
curq := cururl.Query()
curq.Add("doparse", "1")
cururl.RawQuery = curq.Encode()
w.Write([]byte(fmt.Sprintf("<a href=\"%s\">Download Token</a><br/>", cururl.String())))
})
http.ListenAndServe(":14000", nil)
}

View File

@@ -0,0 +1,108 @@
package example
import (
"fmt"
"github.com/RangelReale/osin"
)
type TestStorage struct {
clients map[string]osin.Client
authorize map[string]*osin.AuthorizeData
access map[string]*osin.AccessData
refresh map[string]string
}
func NewTestStorage() *TestStorage {
r := &TestStorage{
clients: make(map[string]osin.Client),
authorize: make(map[string]*osin.AuthorizeData),
access: make(map[string]*osin.AccessData),
refresh: make(map[string]string),
}
r.clients["1234"] = &osin.DefaultClient{
Id: "1234",
Secret: "aabbccdd",
RedirectUri: "http://localhost:14000/appauth",
}
return r
}
func (s *TestStorage) Clone() osin.Storage {
return s
}
func (s *TestStorage) Close() {
}
func (s *TestStorage) GetClient(id string) (osin.Client, error) {
fmt.Printf("GetClient: %s\n", id)
if c, ok := s.clients[id]; ok {
return c, nil
}
return nil, osin.ErrNotFound
}
func (s *TestStorage) SetClient(id string, client osin.Client) error {
fmt.Printf("SetClient: %s\n", id)
s.clients[id] = client
return nil
}
func (s *TestStorage) SaveAuthorize(data *osin.AuthorizeData) error {
fmt.Printf("SaveAuthorize: %s\n", data.Code)
s.authorize[data.Code] = data
return nil
}
func (s *TestStorage) LoadAuthorize(code string) (*osin.AuthorizeData, error) {
fmt.Printf("LoadAuthorize: %s\n", code)
if d, ok := s.authorize[code]; ok {
return d, nil
}
return nil, osin.ErrNotFound
}
func (s *TestStorage) RemoveAuthorize(code string) error {
fmt.Printf("RemoveAuthorize: %s\n", code)
delete(s.authorize, code)
return nil
}
func (s *TestStorage) SaveAccess(data *osin.AccessData) error {
fmt.Printf("SaveAccess: %s\n", data.AccessToken)
s.access[data.AccessToken] = data
if data.RefreshToken != "" {
s.refresh[data.RefreshToken] = data.AccessToken
}
return nil
}
func (s *TestStorage) LoadAccess(code string) (*osin.AccessData, error) {
fmt.Printf("LoadAccess: %s\n", code)
if d, ok := s.access[code]; ok {
return d, nil
}
return nil, osin.ErrNotFound
}
func (s *TestStorage) RemoveAccess(code string) error {
fmt.Printf("RemoveAccess: %s\n", code)
delete(s.access, code)
return nil
}
func (s *TestStorage) LoadRefresh(code string) (*osin.AccessData, error) {
fmt.Printf("LoadRefresh: %s\n", code)
if d, ok := s.refresh[code]; ok {
return s.LoadAccess(d)
}
return nil, osin.ErrNotFound
}
func (s *TestStorage) RemoveRefresh(code string) error {
fmt.Printf("RemoveRefresh: %s\n", code)
delete(s.refresh, code)
return nil
}

81
vendor/github.com/RangelReale/osin/info.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package osin
import (
"net/http"
"time"
)
// InfoRequest is a request for information about some AccessData
type InfoRequest struct {
Code string // Code to look up
AccessData *AccessData // AccessData associated with Code
}
// HandleInfoRequest is an http.HandlerFunc for server information
// NOT an RFC specification.
func (s *Server) HandleInfoRequest(w *Response, r *http.Request) *InfoRequest {
r.ParseForm()
bearer := CheckBearerAuth(r)
if bearer == nil {
w.SetError(E_INVALID_REQUEST, "")
return nil
}
// generate info request
ret := &InfoRequest{
Code: bearer.Code,
}
if ret.Code == "" {
w.SetError(E_INVALID_REQUEST, "")
return nil
}
var err error
// load access data
ret.AccessData, err = w.Storage.LoadAccess(ret.Code)
if err != nil {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = err
return nil
}
if ret.AccessData == nil {
w.SetError(E_INVALID_REQUEST, "")
return nil
}
if ret.AccessData.Client == nil {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AccessData.Client.GetRedirectUri() == "" {
w.SetError(E_UNAUTHORIZED_CLIENT, "")
return nil
}
if ret.AccessData.IsExpiredAt(s.Now()) {
w.SetError(E_INVALID_GRANT, "")
return nil
}
return ret
}
// FinishInfoRequest finalizes the request handled by HandleInfoRequest
func (s *Server) FinishInfoRequest(w *Response, r *http.Request, ir *InfoRequest) {
// don't process if is already an error
if w.IsError {
return
}
// output data
w.Output["client_id"] = ir.AccessData.Client.GetId()
w.Output["access_token"] = ir.AccessData.AccessToken
w.Output["token_type"] = s.Config.TokenType
w.Output["expires_in"] = ir.AccessData.CreatedAt.Add(time.Duration(ir.AccessData.ExpiresIn)*time.Second).Sub(s.Now()) / time.Second
if ir.AccessData.RefreshToken != "" {
w.Output["refresh_token"] = ir.AccessData.RefreshToken
}
if ir.AccessData.Scope != "" {
w.Output["scope"] = ir.AccessData.Scope
}
}

72
vendor/github.com/RangelReale/osin/info_test.go generated vendored Normal file
View File

@@ -0,0 +1,72 @@
package osin
import (
"net/http"
"net/url"
"testing"
)
func TestInfo(t *testing.T) {
sconfig := NewServerConfig()
server := NewServer(sconfig, NewTestingStorage())
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Form = make(url.Values)
req.Form.Set("code", "9999")
if ar := server.HandleInfoRequest(resp, req); ar != nil {
server.FinishInfoRequest(resp, req, ar)
}
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "9999" {
t.Fatalf("Unexpected authorization code: %s", d)
}
}
func TestInfoWhenCodeIsOnHeader(t *testing.T) {
sconfig := NewServerConfig()
server := NewServer(sconfig, NewTestingStorage())
resp := server.NewResponse()
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Authorization", "Bearer 9999")
if ar := server.HandleInfoRequest(resp, req); ar != nil {
server.FinishInfoRequest(resp, req, ar)
}
if resp.IsError && resp.InternalError != nil {
t.Fatalf("Error in response: %s", resp.InternalError)
}
if resp.IsError {
t.Fatalf("Should not be an error")
}
if resp.Type != DATA {
t.Fatalf("Response should be data")
}
if d := resp.Output["access_token"]; d != "9999" {
t.Fatalf("Unexpected authorization code: %s", d)
}
}

151
vendor/github.com/RangelReale/osin/response.go generated vendored Normal file
View File

@@ -0,0 +1,151 @@
package osin
import (
"errors"
"fmt"
"net/http"
"net/url"
)
// Data for response output
type ResponseData map[string]interface{}
// Response type enum
type ResponseType int
const (
DATA ResponseType = iota
REDIRECT
)
// Server response
type Response struct {
Type ResponseType
StatusCode int
StatusText string
ErrorStatusCode int
URL string
Output ResponseData
Headers http.Header
IsError bool
ErrorId string
InternalError error
RedirectInFragment bool
// Storage to use in this response - required
Storage Storage
}
func NewResponse(storage Storage) *Response {
r := &Response{
Type: DATA,
StatusCode: 200,
ErrorStatusCode: 200,
Output: make(ResponseData),
Headers: make(http.Header),
IsError: false,
Storage: storage.Clone(),
}
r.Headers.Add(
"Cache-Control",
"no-cache, no-store, max-age=0, must-revalidate",
)
r.Headers.Add("Pragma", "no-cache")
r.Headers.Add("Expires", "Fri, 01 Jan 1990 00:00:00 GMT")
return r
}
// SetError sets an error id and description on the Response
// state and uri are left blank
func (r *Response) SetError(id string, description string) {
r.SetErrorUri(id, description, "", "")
}
// SetErrorState sets an error id, description, and state on the Response
// uri is left blank
func (r *Response) SetErrorState(id string, description string, state string) {
r.SetErrorUri(id, description, "", state)
}
// SetErrorUri sets an error id, description, state, and uri on the Response
func (r *Response) SetErrorUri(id string, description string, uri string, state string) {
// get default error message
if description == "" {
description = deferror.Get(id)
}
// set error parameters
r.IsError = true
r.ErrorId = id
r.StatusCode = r.ErrorStatusCode
if r.StatusCode != 200 {
r.StatusText = description
} else {
r.StatusText = ""
}
r.Output = make(ResponseData) // clear output
r.Output["error"] = id
r.Output["error_description"] = description
if uri != "" {
r.Output["error_uri"] = uri
}
if state != "" {
r.Output["state"] = state
}
}
// SetRedirect changes the response to redirect to the given url
func (r *Response) SetRedirect(url string) {
// set redirect parameters
r.Type = REDIRECT
r.URL = url
}
// SetRedirectFragment sets redirect values to be passed in fragment instead of as query parameters
func (r *Response) SetRedirectFragment(f bool) {
r.RedirectInFragment = f
}
// GetRedirectUrl returns the redirect url with all query string parameters
func (r *Response) GetRedirectUrl() (string, error) {
if r.Type != REDIRECT {
return "", errors.New("Not a redirect response")
}
u, err := url.Parse(r.URL)
if err != nil {
return "", err
}
var q url.Values
if r.RedirectInFragment {
// start with empty set for fragment
q = url.Values{}
} else {
// add parameters to existing query
q = u.Query()
}
// add parameters
for n, v := range r.Output {
q.Set(n, fmt.Sprint(v))
}
// https://tools.ietf.org/html/rfc6749#section-4.2.2
// Fragment should be encoded as application/x-www-form-urlencoded (%-escaped, spaces are represented as '+')
// The stdlib URL#String() doesn't make that easy to accomplish, so build this ourselves
if r.RedirectInFragment {
u.Fragment = ""
redirectURI := u.String() + "#" + q.Encode()
return redirectURI, nil
}
// Otherwise, update the query and encode normally
u.RawQuery = q.Encode()
u.Fragment = ""
return u.String(), nil
}
func (r *Response) Close() {
r.Storage.Close()
}

39
vendor/github.com/RangelReale/osin/response_json.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package osin
import (
"encoding/json"
"net/http"
)
// OutputJSON encodes the Response to JSON and writes to the http.ResponseWriter
func OutputJSON(rs *Response, w http.ResponseWriter, r *http.Request) error {
// Add headers
for i, k := range rs.Headers {
for _, v := range k {
w.Header().Add(i, v)
}
}
if rs.Type == REDIRECT {
// Output redirect with parameters
u, err := rs.GetRedirectUrl()
if err != nil {
return err
}
w.Header().Add("Location", u)
w.WriteHeader(302)
} else {
// set content type if the response doesn't already have one associated with it
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "application/json")
}
w.WriteHeader(rs.StatusCode)
encoder := json.NewEncoder(w)
err := encoder.Encode(rs.Output)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,115 @@
package osin
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestResponseJSON(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
r := NewResponse(NewTestingStorage())
r.Output["access_token"] = "1234"
r.Output["token_type"] = "5678"
err = OutputJSON(r, w, req)
if err != nil {
t.Fatalf("Error outputting json: %s", err)
}
//fmt.Printf("%d - %s - %+v", w.Code, w.Body.String(), w.HeaderMap)
if w.Code != 200 {
t.Fatalf("Invalid response code for output: %d", w.Code)
}
if w.HeaderMap.Get("Content-Type") != "application/json" {
t.Fatalf("Result from json must be application/json")
}
// parse output json
output := make(map[string]interface{})
if err := json.Unmarshal(w.Body.Bytes(), &output); err != nil {
t.Fatalf("Could not decode output json: %s", err)
}
if d, ok := output["access_token"]; !ok || d != "1234" {
t.Fatalf("Invalid or not found output data: access_token=%s", d)
}
if d, ok := output["token_type"]; !ok || d != "5678" {
t.Fatalf("Invalid or not found output data: token_type=%s", d)
}
}
func TestErrorResponseJSON(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
r := NewResponse(NewTestingStorage())
r.ErrorStatusCode = 500
r.SetError(E_INVALID_REQUEST, "")
err = OutputJSON(r, w, req)
if err != nil {
t.Fatalf("Error outputting json: %s", err)
}
//fmt.Printf("%d - %s - %+v", w.Code, w.Body.String(), w.HeaderMap)
if w.Code != 500 {
t.Fatalf("Invalid response code for error output: %d", w.Code)
}
if w.HeaderMap.Get("Content-Type") != "application/json" {
t.Fatalf("Result from json must be application/json")
}
// parse output json
output := make(map[string]interface{})
if err := json.Unmarshal(w.Body.Bytes(), &output); err != nil {
t.Fatalf("Could not decode output json: %s", err)
}
if d, ok := output["error"]; !ok || d != E_INVALID_REQUEST {
t.Fatalf("Invalid or not found output data: error=%s", d)
}
}
func TestRedirectResponseJSON(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost:14000/appauth", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
r := NewResponse(NewTestingStorage())
r.SetRedirect("http://localhost:14000")
err = OutputJSON(r, w, req)
if err != nil {
t.Fatalf("Error outputting json: %s", err)
}
//fmt.Printf("%d - %s - %+v", w.Code, w.Body.String(), w.HeaderMap)
if w.Code != 302 {
t.Fatalf("Invalid response code for redirect output: %d", w.Code)
}
if w.HeaderMap.Get("Location") != "http://localhost:14000" {
t.Fatalf("Invalid response location url: %s", w.HeaderMap.Get("Location"))
}
}

74
vendor/github.com/RangelReale/osin/response_test.go generated vendored Normal file
View File

@@ -0,0 +1,74 @@
package osin
import (
"net/url"
"strings"
"testing"
)
func TestGetRedirectUrl(t *testing.T) {
// Make sure we can round-trip state parameters containing special URL characters, both as a query param and in an encoded fragment
state := `{"then": "/index.html?a=1&b=%2B#fragment", "nonce": "014f:bff9a07c"}`
testcases := map[string]struct {
URL string
Output ResponseData
RedirectInFragment bool
ExpectedURL string
}{
"query": {
URL: "https://foo.com/path?abc=123",
Output: ResponseData{"access_token": "12345", "state": state},
ExpectedURL: "https://foo.com/path?abc=123&access_token=12345&state=%7B%22then%22%3A+%22%2Findex.html%3Fa%3D1%26b%3D%252B%23fragment%22%2C+%22nonce%22%3A+%22014f%3Abff9a07c%22%7D",
},
// https://tools.ietf.org/html/rfc6749#section-4.2.2
// Fragment should be encoded as application/x-www-form-urlencoded (%-escaped, spaces are represented as '+')
"fragment": {
URL: "https://foo.com/path?abc=123",
Output: ResponseData{"access_token": "12345", "state": state},
RedirectInFragment: true,
ExpectedURL: "https://foo.com/path?abc=123#access_token=12345&state=%7B%22then%22%3A+%22%2Findex.html%3Fa%3D1%26b%3D%252B%23fragment%22%2C+%22nonce%22%3A+%22014f%3Abff9a07c%22%7D",
},
}
for k, tc := range testcases {
resp := &Response{
Type: REDIRECT,
URL: tc.URL,
Output: tc.Output,
RedirectInFragment: tc.RedirectInFragment,
}
result, err := resp.GetRedirectUrl()
if err != nil {
t.Errorf("%s: %v", k, err)
continue
}
if result != tc.ExpectedURL {
t.Errorf("%s: expected\n\t%v, got\n\t%v", k, tc.ExpectedURL, result)
continue
}
var params url.Values
if tc.RedirectInFragment {
params, err = url.ParseQuery(strings.SplitN(result, "#", 2)[1])
if err != nil {
t.Errorf("%s: %v", k, err)
continue
}
} else {
parsedResult, err := url.Parse(result)
if err != nil {
t.Errorf("%s: %v", k, err)
continue
}
params = parsedResult.Query()
}
if params["state"][0] != state {
t.Errorf("%s: expected\n\t%v, got\n\t%v", k, state, params["state"][0])
continue
}
}
}

32
vendor/github.com/RangelReale/osin/server.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package osin
import (
"time"
)
// Server is an OAuth2 implementation
type Server struct {
Config *ServerConfig
Storage Storage
AuthorizeTokenGen AuthorizeTokenGen
AccessTokenGen AccessTokenGen
Now func() time.Time
}
// NewServer creates a new server instance
func NewServer(config *ServerConfig, storage Storage) *Server {
return &Server{
Config: config,
Storage: storage,
AuthorizeTokenGen: &AuthorizeTokenGenDefault{},
AccessTokenGen: &AccessTokenGenDefault{},
Now: time.Now,
}
}
// NewResponse creates a new response for the server
func (s *Server) NewResponse() *Response {
r := NewResponse(s.Storage)
r.ErrorStatusCode = s.Config.ErrorStatusCode
return r
}

59
vendor/github.com/RangelReale/osin/storage.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
package osin
import (
"errors"
)
var (
// ErrNotFound is the error returned by Storage Get<...> and Load<...> functions in case
// no entity is found in the storage. E.g. Storage.GetClient() returns ErrNotFound when
// client is not found. All other returned errors must be treated as storage-specific errors,
// like "connection lost", "connection refused", etc.
ErrNotFound = errors.New("Entity not found")
)
// Storage interface
type Storage interface {
// Clone the storage if needed. For example, using mgo, you can clone the session with session.Clone
// to avoid concurrent access problems.
// This is to avoid cloning the connection at each method access.
// Can return itself if not a problem.
Clone() Storage
// Close the resources the Storage potentially holds (using Clone for example)
Close()
// GetClient loads the client by id (client_id)
GetClient(id string) (Client, error)
// SaveAuthorize saves authorize data.
SaveAuthorize(*AuthorizeData) error
// LoadAuthorize looks up AuthorizeData by a code.
// Client information MUST be loaded together.
// Optionally can return error if expired.
LoadAuthorize(code string) (*AuthorizeData, error)
// RemoveAuthorize revokes or deletes the authorization code.
RemoveAuthorize(code string) error
// SaveAccess writes AccessData.
// If RefreshToken is not blank, it must save in a way that can be loaded using LoadRefresh.
SaveAccess(*AccessData) error
// LoadAccess retrieves access data by token. Client information MUST be loaded together.
// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
// Optionally can return error if expired.
LoadAccess(token string) (*AccessData, error)
// RemoveAccess revokes or deletes an AccessData.
RemoveAccess(token string) error
// LoadRefresh retrieves refresh AccessData. Client information MUST be loaded together.
// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
// Optionally can return error if expired.
LoadRefresh(token string) (*AccessData, error)
// RemoveRefresh revokes or deletes refresh AccessData.
RemoveRefresh(token string) error
}

158
vendor/github.com/RangelReale/osin/storage_test.go generated vendored Normal file
View File

@@ -0,0 +1,158 @@
package osin
import (
"strconv"
"time"
)
type TestingStorage struct {
clients map[string]Client
authorize map[string]*AuthorizeData
access map[string]*AccessData
refresh map[string]string
}
func NewTestingStorage() *TestingStorage {
r := &TestingStorage{
clients: make(map[string]Client),
authorize: make(map[string]*AuthorizeData),
access: make(map[string]*AccessData),
refresh: make(map[string]string),
}
r.clients["1234"] = &DefaultClient{
Id: "1234",
Secret: "aabbccdd",
RedirectUri: "http://localhost:14000/appauth",
}
r.clients["public-client"] = &DefaultClient{
Id: "public-client",
RedirectUri: "http://localhost:14000/appauth",
}
r.authorize["9999"] = &AuthorizeData{
Client: r.clients["1234"],
Code: "9999",
ExpiresIn: 3600,
CreatedAt: time.Now(),
RedirectUri: "http://localhost:14000/appauth",
}
r.access["9999"] = &AccessData{
Client: r.clients["1234"],
AuthorizeData: r.authorize["9999"],
AccessToken: "9999",
ExpiresIn: 3600,
CreatedAt: time.Now(),
}
r.access["r9999"] = &AccessData{
Client: r.clients["1234"],
AuthorizeData: r.authorize["9999"],
AccessData: r.access["9999"],
AccessToken: "9999",
RefreshToken: "r9999",
ExpiresIn: 3600,
CreatedAt: time.Now(),
}
r.refresh["r9999"] = "9999"
return r
}
func (s *TestingStorage) Clone() Storage {
return s
}
func (s *TestingStorage) Close() {
}
func (s *TestingStorage) GetClient(id string) (Client, error) {
if c, ok := s.clients[id]; ok {
return c, nil
}
return nil, ErrNotFound
}
func (s *TestingStorage) SetClient(id string, client Client) error {
s.clients[id] = client
return nil
}
func (s *TestingStorage) SaveAuthorize(data *AuthorizeData) error {
s.authorize[data.Code] = data
return nil
}
func (s *TestingStorage) LoadAuthorize(code string) (*AuthorizeData, error) {
if d, ok := s.authorize[code]; ok {
return d, nil
}
return nil, ErrNotFound
}
func (s *TestingStorage) RemoveAuthorize(code string) error {
delete(s.authorize, code)
return nil
}
func (s *TestingStorage) SaveAccess(data *AccessData) error {
s.access[data.AccessToken] = data
if data.RefreshToken != "" {
s.refresh[data.RefreshToken] = data.AccessToken
}
return nil
}
func (s *TestingStorage) LoadAccess(code string) (*AccessData, error) {
if d, ok := s.access[code]; ok {
return d, nil
}
return nil, ErrNotFound
}
func (s *TestingStorage) RemoveAccess(code string) error {
delete(s.access, code)
return nil
}
func (s *TestingStorage) LoadRefresh(code string) (*AccessData, error) {
if d, ok := s.refresh[code]; ok {
return s.LoadAccess(d)
}
return nil, ErrNotFound
}
func (s *TestingStorage) RemoveRefresh(code string) error {
delete(s.refresh, code)
return nil
}
// Predictable testing token generation
type TestingAuthorizeTokenGen struct {
counter int64
}
func (a *TestingAuthorizeTokenGen) GenerateAuthorizeToken(data *AuthorizeData) (ret string, err error) {
a.counter++
return strconv.FormatInt(a.counter, 10), nil
}
type TestingAccessTokenGen struct {
acounter int64
rcounter int64
}
func (a *TestingAccessTokenGen) GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) {
a.acounter++
accesstoken = strconv.FormatInt(a.acounter, 10)
if generaterefresh {
a.rcounter++
refreshtoken = "r" + strconv.FormatInt(a.rcounter, 10)
}
return
}

33
vendor/github.com/RangelReale/osin/tokengen.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
package osin
import (
"encoding/base64"
"github.com/pborman/uuid"
)
// AuthorizeTokenGenDefault is the default authorization token generator
type AuthorizeTokenGenDefault struct {
}
// GenerateAuthorizeToken generates a base64-encoded UUID code
func (a *AuthorizeTokenGenDefault) GenerateAuthorizeToken(data *AuthorizeData) (ret string, err error) {
token := uuid.NewRandom()
return base64.RawURLEncoding.EncodeToString([]byte(token)), nil
}
// AccessTokenGenDefault is the default authorization token generator
type AccessTokenGenDefault struct {
}
// GenerateAccessToken generates base64-encoded UUID access and refresh tokens
func (a *AccessTokenGenDefault) GenerateAccessToken(data *AccessData, generaterefresh bool) (accesstoken string, refreshtoken string, err error) {
token := uuid.NewRandom()
accesstoken = base64.RawURLEncoding.EncodeToString([]byte(token))
if generaterefresh {
rtoken := uuid.NewRandom()
refreshtoken = base64.RawURLEncoding.EncodeToString([]byte(rtoken))
}
return
}

114
vendor/github.com/RangelReale/osin/urivalidate.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package osin
import (
"errors"
"fmt"
"net/url"
"strings"
)
// error returned when validation don't match
type UriValidationError string
func (e UriValidationError) Error() string {
return string(e)
}
func newUriValidationError(msg string, base string, redirect string) UriValidationError {
return UriValidationError(fmt.Sprintf("%s: %s / %s", msg, base, redirect))
}
// ValidateUriList validates that redirectUri is contained in baseUriList.
// baseUriList may be a string separated by separator.
// If separator is blank, validate only 1 URI.
func ValidateUriList(baseUriList string, redirectUri string, separator string) error {
// make a list of uris
var slist []string
if separator != "" {
slist = strings.Split(baseUriList, separator)
} else {
slist = make([]string, 0)
slist = append(slist, baseUriList)
}
for _, sitem := range slist {
err := ValidateUri(sitem, redirectUri)
// validated, return no error
if err == nil {
return nil
}
// if there was an error that is not a validation error, return it
if _, iok := err.(UriValidationError); !iok {
return err
}
}
return newUriValidationError("urls don't validate", baseUriList, redirectUri)
}
// ValidateUri validates that redirectUri is contained in baseUri
func ValidateUri(baseUri string, redirectUri string) error {
if baseUri == "" || redirectUri == "" {
return errors.New("urls cannot be blank.")
}
// parse base url
base, err := url.Parse(baseUri)
if err != nil {
return err
}
// parse passed url
redirect, err := url.Parse(redirectUri)
if err != nil {
return err
}
// must not have fragment
if base.Fragment != "" || redirect.Fragment != "" {
return errors.New("url must not include fragment.")
}
// check if urls match
if base.Scheme != redirect.Scheme {
return newUriValidationError("scheme mismatch", baseUri, redirectUri)
}
if base.Host != redirect.Host {
return newUriValidationError("host mismatch", baseUri, redirectUri)
}
// allow exact path matches
if base.Path == redirect.Path {
return nil
}
// ensure prefix matches are actually subpaths
requiredPrefix := strings.TrimRight(base.Path, "/") + "/"
if !strings.HasPrefix(redirect.Path, requiredPrefix) {
return newUriValidationError("path is not a subpath", baseUri, redirectUri)
}
// ensure prefix matches don't contain path traversals
for _, s := range strings.Split(strings.TrimPrefix(redirect.Path, requiredPrefix), "/") {
if s == ".." {
return newUriValidationError("subpath cannot contain path traversal", baseUri, redirectUri)
}
}
return nil
}
// Returns the first uri from an uri list
func FirstUri(baseUriList string, separator string) string {
if separator != "" {
slist := strings.Split(baseUriList, separator)
if len(slist) > 0 {
return slist[0]
}
} else {
return baseUriList
}
return ""
}

120
vendor/github.com/RangelReale/osin/urivalidate_test.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
package osin
import (
"testing"
)
func TestURIValidate(t *testing.T) {
valid := [][]string{
{
// Exact match
"http://localhost:14000/appauth",
"http://localhost:14000/appauth",
},
{
// Trailing slash
"http://www.google.com/myapp",
"http://www.google.com/myapp/",
},
{
// Exact match with trailing slash
"http://www.google.com/myapp/",
"http://www.google.com/myapp/",
},
{
// Subpath
"http://www.google.com/myapp",
"http://www.google.com/myapp/interface/implementation",
},
{
// Subpath with trailing slash
"http://www.google.com/myapp/",
"http://www.google.com/myapp/interface/implementation",
},
{
// Subpath with things that are close to path traversals, but aren't
"http://www.google.com/myapp",
"http://www.google.com/myapp/.../..implementation../...",
},
{
// If the allowed basepath contains path traversals, allow them?
"http://www.google.com/traversal/../allowed",
"http://www.google.com/traversal/../allowed/with/subpath",
},
}
for _, v := range valid {
if err := ValidateUri(v[0], v[1]); err != nil {
t.Errorf("Expected ValidateUri(%s, %s) to succeed, got %v", v[0], v[1], err)
}
}
invalid := [][]string{
{
// Doesn't satisfy base path
"http://localhost:14000/appauth",
"http://localhost:14000/app",
},
{
// Doesn't satisfy base path
"http://localhost:14000/app/",
"http://localhost:14000/app",
},
{
// Not a subpath of base path
"http://localhost:14000/appauth",
"http://localhost:14000/appauthmodifiedpath",
},
{
// Host mismatch
"http://www.google.com/myapp",
"http://www2.google.com/myapp",
},
{
// Scheme mismatch
"http://www.google.com/myapp",
"https://www.google.com/myapp",
},
{
// Path traversal
"http://www.google.com/myapp",
"http://www.google.com/myapp/..",
},
{
// Embedded path traversal
"http://www.google.com/myapp",
"http://www.google.com/myapp/../test",
},
{
// Not a subpath
"http://www.google.com/myapp",
"http://www.google.com/myapp../test",
},
}
for _, v := range invalid {
if err := ValidateUri(v[0], v[1]); err == nil {
t.Errorf("Expected ValidateUri(%s, %s) to fail", v[0], v[1])
}
}
}
func TestURIListValidate(t *testing.T) {
// V1
if err := ValidateUriList("http://localhost:14000/appauth", "http://localhost:14000/appauth", ""); err != nil {
t.Errorf("V1: %s", err)
}
// V2
if err := ValidateUriList("http://localhost:14000/appauth", "http://localhost:14000/app", ""); err == nil {
t.Error("V2 should have failed")
}
// V3
if err := ValidateUriList("http://xxx:14000/appauth;http://localhost:14000/appauth", "http://localhost:14000/appauth", ";"); err != nil {
t.Errorf("V3: %s", err)
}
// V4
if err := ValidateUriList("http://xxx:14000/appauth;http://localhost:14000/appauth", "http://localhost:14000/app", ";"); err == nil {
t.Error("V4 should have failed")
}
}

108
vendor/github.com/RangelReale/osin/util.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
package osin
import (
"encoding/base64"
"errors"
"net/http"
"strings"
)
// Parse basic authentication header
type BasicAuth struct {
Username string
Password string
}
// Parse bearer authentication header
type BearerAuth struct {
Code string
}
// CheckClientSecret determines whether the given secret matches a secret held by the client.
// Public clients return true for a secret of ""
func CheckClientSecret(client Client, secret string) bool {
switch client := client.(type) {
case ClientSecretMatcher:
// Prefer the more secure method of giving the secret to the client for comparison
return client.ClientSecretMatches(secret)
default:
// Fallback to the less secure method of extracting the plain text secret from the client for comparison
return client.GetSecret() == secret
}
}
// Return authorization header data
func CheckBasicAuth(r *http.Request) (*BasicAuth, error) {
if r.Header.Get("Authorization") == "" {
return nil, nil
}
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 || s[0] != "Basic" {
return nil, errors.New("Invalid authorization header")
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return nil, err
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return nil, errors.New("Invalid authorization message")
}
return &BasicAuth{Username: pair[0], Password: pair[1]}, nil
}
// Return "Bearer" token from request. The header has precedence over query string.
func CheckBearerAuth(r *http.Request) *BearerAuth {
authHeader := r.Header.Get("Authorization")
authForm := r.Form.Get("code")
if authHeader == "" && authForm == "" {
return nil
}
token := authForm
if authHeader != "" {
s := strings.SplitN(authHeader, " ", 2)
if (len(s) != 2 || strings.ToLower(s[0]) != "bearer") && token == "" {
return nil
}
//Use authorization header token only if token type is bearer else query string access token would be returned
if len(s) > 0 && strings.ToLower(s[0]) == "bearer" {
token = s[1]
}
}
return &BearerAuth{Code: token}
}
// getClientAuth checks client basic authentication in params if allowed,
// otherwise gets it from the header.
// Sets an error on the response if no auth is present or a server error occurs.
func getClientAuth(w *Response, r *http.Request, allowQueryParams bool) *BasicAuth {
if allowQueryParams {
// Allow for auth without password
if _, hasSecret := r.Form["client_secret"]; hasSecret {
auth := &BasicAuth{
Username: r.Form.Get("client_id"),
Password: r.Form.Get("client_secret"),
}
if auth.Username != "" {
return auth
}
}
}
auth, err := CheckBasicAuth(r)
if err != nil {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = err
return nil
}
if auth == nil {
w.SetError(E_INVALID_REQUEST, "")
w.InternalError = errors.New("Client authentication not sent")
return nil
}
return auth
}

136
vendor/github.com/RangelReale/osin/util_test.go generated vendored Normal file
View File

@@ -0,0 +1,136 @@
package osin
import (
"net/http"
"net/url"
"testing"
)
const (
badAuthValue = "Digest XHHHHHHH"
goodAuthValue = "Basic dGVzdDp0ZXN0"
goodBearerAuthValue = "Bearer BGFVTDUJDp0ZXN0"
)
func TestBasicAuth(t *testing.T) {
r := &http.Request{Header: make(http.Header)}
// Without any header
if b, err := CheckBasicAuth(r); b != nil || err != nil {
t.Errorf("Validated basic auth without header")
}
// with invalid header
r.Header.Set("Authorization", badAuthValue)
b, err := CheckBasicAuth(r)
if b != nil || err == nil {
t.Errorf("Validated invalid auth")
return
}
// with valid header
r.Header.Set("Authorization", goodAuthValue)
b, err = CheckBasicAuth(r)
if b == nil || err != nil {
t.Errorf("Could not extract basic auth")
return
}
// check extracted auth data
if b.Username != "test" || b.Password != "test" {
t.Errorf("Error decoding basic auth")
}
}
func TestGetClientAuth(t *testing.T) {
urlWithSecret, _ := url.Parse("http://host.tld/path?client_id=xxx&client_secret=yyy")
urlWithEmptySecret, _ := url.Parse("http://host.tld/path?client_id=xxx&client_secret=")
urlNoSecret, _ := url.Parse("http://host.tld/path?client_id=xxx")
headerNoAuth := make(http.Header)
headerBadAuth := make(http.Header)
headerBadAuth.Set("Authorization", badAuthValue)
headerOKAuth := make(http.Header)
headerOKAuth.Set("Authorization", goodAuthValue)
var tests = []struct {
header http.Header
url *url.URL
allowQueryParams bool
expectAuth bool
}{
{headerNoAuth, urlWithSecret, true, true},
{headerNoAuth, urlWithSecret, false, false},
{headerNoAuth, urlWithEmptySecret, true, true},
{headerNoAuth, urlWithEmptySecret, false, false},
{headerNoAuth, urlNoSecret, true, false},
{headerNoAuth, urlNoSecret, false, false},
{headerBadAuth, urlWithSecret, true, true},
{headerBadAuth, urlWithSecret, false, false},
{headerBadAuth, urlWithEmptySecret, true, true},
{headerBadAuth, urlWithEmptySecret, false, false},
{headerBadAuth, urlNoSecret, true, false},
{headerBadAuth, urlNoSecret, false, false},
{headerOKAuth, urlWithSecret, true, true},
{headerOKAuth, urlWithSecret, false, true},
{headerOKAuth, urlWithEmptySecret, true, true},
{headerOKAuth, urlWithEmptySecret, false, true},
{headerOKAuth, urlNoSecret, true, true},
{headerOKAuth, urlNoSecret, false, true},
}
for _, tt := range tests {
w := new(Response)
r := &http.Request{Header: tt.header, URL: tt.url}
r.ParseForm()
auth := getClientAuth(w, r, tt.allowQueryParams)
if tt.expectAuth && auth == nil {
t.Errorf("Auth should not be nil for %v", tt)
} else if !tt.expectAuth && auth != nil {
t.Errorf("Auth should be nil for %v", tt)
}
}
}
func TestBearerAuth(t *testing.T) {
r := &http.Request{Header: make(http.Header)}
// Without any header
if b := CheckBearerAuth(r); b != nil {
t.Errorf("Validated bearer auth without header")
}
// with invalid header
r.Header.Set("Authorization", badAuthValue)
b := CheckBearerAuth(r)
if b != nil {
t.Errorf("Validated invalid auth")
return
}
// with valid header
r.Header.Set("Authorization", goodBearerAuthValue)
b = CheckBearerAuth(r)
if b == nil {
t.Errorf("Could not extract bearer auth")
return
}
// check extracted auth data
if b.Code != "BGFVTDUJDp0ZXN0" {
t.Errorf("Error decoding bearer auth")
}
// extracts bearer auth from query string
url, _ := url.Parse("http://host.tld/path?code=XYZ")
r = &http.Request{URL: url}
r.ParseForm()
b = CheckBearerAuth(r)
if b.Code != "XYZ" {
t.Errorf("Error decoding bearer auth")
}
}