1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2024-11-30 08:06:52 +02:00

cleaned up the token implementation for #1175

This commit is contained in:
Brad Rydzewski 2015-09-09 14:05:52 -07:00
parent cf953f19d3
commit cdfec98cf4
46 changed files with 871 additions and 1002 deletions

4
Godeps/Godeps.json generated
View File

@ -25,8 +25,8 @@
}, },
{ {
"ImportPath": "github.com/dgrijalva/jwt-go", "ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v2.2.0-16-gc48cfd5", "Comment": "v2.3.0-4-gc1da563",
"Rev": "c48cfd5d9711c75acb6036d2698ef3aef7bb655a" "Rev": "c1da56349675b292d3200463e2c88b9aa5e02391"
}, },
{ {
"ImportPath": "github.com/elazarl/go-bindata-assetfs", "ImportPath": "github.com/elazarl/go-bindata-assetfs",

View File

@ -0,0 +1,7 @@
language: go
go:
- 1.3.3
- 1.4.2
- 1.5
- tip

View File

@ -1,5 +1,7 @@
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-jones-json-web-token.html) A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-jones-json-web-token.html)
[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go)
**NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect. **NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect.
## What the heck is a JWT? ## What the heck is a JWT?
@ -21,8 +23,8 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token
```go ```go
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect: // Don't forget to validate the alg is what you expect:
if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"]) return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
} }
return myLookupKey(token.Header["kid"]) return myLookupKey(token.Header["kid"])
}) })
@ -46,12 +48,20 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token
tokenString, err := token.SignedString(mySigningKey) tokenString, err := token.SignedString(mySigningKey)
``` ```
## Extensions
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.
Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go
## Project Status & Versioning ## Project Status & Versioning
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason). This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning.
## More ## More
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).

View File

@ -1,5 +1,10 @@
## `jwt-go` Version History ## `jwt-go` Version History
#### 2.3.0
* Added support for ECDSA signing methods
* Added support for RSA PSS signing methods (requires go v1.4)
#### 2.2.0 #### 2.2.0
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic. * Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.

View File

@ -15,7 +15,7 @@ import (
"os" "os"
"regexp" "regexp"
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
) )
var ( var (

View File

@ -0,0 +1,136 @@
package jwt
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"encoding/asn1"
"errors"
"math/big"
)
var (
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
)
// Implements the ECDSA family of signing methods signing methods
type SigningMethodECDSA struct {
Name string
Hash crypto.Hash
}
// Marshalling structure for r, s EC point
type ECPoint struct {
R *big.Int
S *big.Int
}
// Specific instances for EC256 and company
var (
SigningMethodES256 *SigningMethodECDSA
SigningMethodES384 *SigningMethodECDSA
SigningMethodES512 *SigningMethodECDSA
)
func init() {
// ES256
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256}
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
return SigningMethodES256
})
// ES384
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384}
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
return SigningMethodES384
})
// ES512
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512}
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
return SigningMethodES512
})
}
func (m *SigningMethodECDSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an ecdsa.PublicKey struct
func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
// Get the key
var ecdsaKey *ecdsa.PublicKey
switch k := key.(type) {
case *ecdsa.PublicKey:
ecdsaKey = k
default:
return ErrInvalidKey
}
// Unmarshal asn1 ECPoint
var ecpoint = new(ECPoint)
if _, err := asn1.Unmarshal(sig, ecpoint); err != nil {
return err
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), ecpoint.R, ecpoint.S); verifystatus == true {
return nil
} else {
return ErrECDSAVerification
}
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an ecdsa.PrivateKey struct
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
// Get the key
var ecdsaKey *ecdsa.PrivateKey
switch k := key.(type) {
case *ecdsa.PrivateKey:
ecdsaKey = k
default:
return "", ErrInvalidKey
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return r, s
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
// asn1 marhsal r, s using ecPoint as the structure
var ecpoint = new(ECPoint)
ecpoint.R = r
ecpoint.S = s
if signature, err := asn1.Marshal(*ecpoint); err != nil {
return "", err
} else {
return EncodeSegment(signature), nil
}
} else {
return "", err
}
}

View File

@ -0,0 +1,100 @@
package jwt_test
import (
"crypto/ecdsa"
"io/ioutil"
"strings"
"testing"
"github.com/dgrijalva/jwt-go"
)
var ecdsaTestData = []struct {
name string
keys map[string]string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic ES256",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8w",
"ES256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES384",
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MGUCMQCHBr61FXDuFY9xUhyp8iWQAuBIaSgaf1z2j_8XrKcCfzTPzoSa3SZKq-m3L492xe8CMG3kafRMeuaN5Aw8ZJxmOLhkTo4D3-LaGzcaUWINvWvkwFMl7dMC863s0gov6xvXuA",
"ES384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES512",
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
"eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MIGIAkIAmVKjdJE5lG1byOFgZZVTeNDRp6E7SNvUj0UrvpzoBH6nrleWVTcwfHzbwWuooNpPADDSFR_Ql3ze-Vwwi8hBqQsCQgHn-ZooL8zegkOVeEEsqd7WHWdhb8UekFCYw3X8JnNP-D3wvZQ1-tkkHakt5gZ2-xO29TxfSPun4ViGkMYa7Q4N-Q",
"ES512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic ES256 invalid: foo => bar",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W",
"ES256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestECDSAVerify(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["public"])
var ecdsaKey *ecdsa.PublicKey
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA public key: %v", err)
}
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestECDSASign(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["private"])
var ecdsaKey *ecdsa.PrivateKey
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA private key: %v", err)
}
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] {
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
}
}
}
}

View File

@ -0,0 +1,67 @@
package jwt
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
)
// Parse PEM encoded Elliptic Curve Private Key Structure
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
return nil, err
}
var pkey *ecdsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
return nil, ErrNotECPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *ecdsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
return nil, ErrNotECPublicKey
}
return pkey, nil
}

View File

@ -0,0 +1,43 @@
package jwt
import (
"errors"
)
// Error constants
var (
ErrInvalidKey = errors.New("key is invalid or of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
ErrNoTokenInRequest = errors.New("no token present in request")
)
// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed
ValidationErrorExpired // Exp validation failed
ValidationErrorNotValidYet // NBF validation failed
)
// The error from Parse if token is not valid
type ValidationError struct {
err string
Errors uint32 // bitfield. see ValidationError... constants
}
// Validation error is an error type
func (e ValidationError) Error() string {
if e.err == "" {
return "token is invalid"
}
return e.err
}
// No errors
func (e *ValidationError) valid() bool {
if e.Errors > 0 {
return false
}
return true
}

View File

@ -2,7 +2,7 @@ package jwt_test
import ( import (
"fmt" "fmt"
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"time" "time"
) )

View File

@ -1,7 +1,7 @@
package jwt_test package jwt_test
import ( import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"io/ioutil" "io/ioutil"
"strings" "strings"
"testing" "testing"
@ -77,3 +77,15 @@ func TestHMACSign(t *testing.T) {
} }
} }
} }
func BenchmarkHS256Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey)
}
func BenchmarkHS384Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey)
}
func BenchmarkHS512Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey)
}

View File

@ -3,7 +3,6 @@ package jwt
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -20,13 +19,6 @@ var TimeFunc = time.Now
// Header of the token (such as `kid`) to identify which key to use. // Header of the token (such as `kid`) to identify which key to use.
type Keyfunc func(*Token) (interface{}, error) type Keyfunc func(*Token) (interface{}, error)
// Error constants
var (
ErrInvalidKey = errors.New("key is invalid or of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
ErrNoTokenInRequest = errors.New("no token present in request")
)
// A JWT Token. Different fields will be used depending on whether you're // A JWT Token. Different fields will be used depending on whether you're
// creating or parsing/verifying a token. // creating or parsing/verifying a token.
type Token struct { type Token struct {
@ -167,37 +159,6 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return token, vErr return token, vErr
} }
// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed
ValidationErrorExpired // Exp validation failed
ValidationErrorNotValidYet // NBF validation failed
)
// The error from Parse if token is not valid
type ValidationError struct {
err string
Errors uint32 // bitfield. see ValidationError... constants
}
// Validation error is an error type
func (e ValidationError) Error() string {
if e.err == "" {
return "token is invalid"
}
return e.err
}
// No errors
func (e *ValidationError) valid() bool {
if e.Errors > 0 {
return false
}
return true
}
// Try to find the token in an http.Request. // Try to find the token in an http.Request.
// This method will call ParseMultipartForm if there's no token in the header. // This method will call ParseMultipartForm if there's no token in the header.
// Currently, it looks in the Authorization header as well as // Currently, it looks in the Authorization header as well as

View File

@ -2,7 +2,7 @@ package jwt_test
import ( import (
"fmt" "fmt"
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"reflect" "reflect"
@ -172,3 +172,16 @@ func TestParseRequest(t *testing.T) {
} }
} }
} }
// Helper method for benchmarking various methods
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) {
t := jwt.New(method)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if _, err := t.SignedString(key); err != nil {
b.Fatal(err)
}
}
})
}

View File

@ -0,0 +1,126 @@
// +build go1.4
package jwt
import (
"crypto"
"crypto/rand"
"crypto/rsa"
)
// Implements the RSAPSS family of signing methods signing methods
type SigningMethodRSAPSS struct {
*SigningMethodRSA
Options *rsa.PSSOptions
}
// Specific instances for RS/PS and company
var (
SigningMethodPS256 *SigningMethodRSAPSS
SigningMethodPS384 *SigningMethodRSAPSS
SigningMethodPS512 *SigningMethodRSAPSS
)
func init() {
// PS256
SigningMethodPS256 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS256",
Hash: crypto.SHA256,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA256,
},
}
RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod {
return SigningMethodPS256
})
// PS384
SigningMethodPS384 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS384",
Hash: crypto.SHA384,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA384,
},
}
RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod {
return SigningMethodPS384
})
// PS512
SigningMethodPS512 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS512",
Hash: crypto.SHA512,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA512,
},
}
RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod {
return SigningMethodPS512
})
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an rsa.PrivateKey struct
func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
var rsaKey *rsa.PublicKey
switch k := key.(type) {
case *rsa.PublicKey:
rsaKey = k
default:
return ErrInvalidKey
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options)
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an rsa.PublicKey struct
func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
switch k := key.(type) {
case *rsa.PrivateKey:
rsaKey = k
default:
return "", ErrInvalidKey
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}

View File

@ -0,0 +1,96 @@
// +build go1.4
package jwt_test
import (
"crypto/rsa"
"io/ioutil"
"strings"
"testing"
"github.com/dgrijalva/jwt-go"
)
var rsaPSSTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic PS256",
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w",
"PS256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic PS384",
"eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ",
"PS384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic PS512",
"eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A",
"PS512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic PS256 invalid: foo => bar",
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W",
"PS256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestRSAPSSVerify(t *testing.T) {
var err error
key, _ := ioutil.ReadFile("test/sample_key.pub")
var rsaPSSKey *rsa.PublicKey
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA public key: %v", err)
}
for _, data := range rsaPSSTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestRSAPSSSign(t *testing.T) {
var err error
key, _ := ioutil.ReadFile("test/sample_key")
var rsaPSSKey *rsa.PrivateKey
if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA private key: %v", err)
}
for _, data := range rsaPSSTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), rsaPSSKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] {
t.Errorf("[%v] Signatures shouldn't match\nnew:\n%v\noriginal:\n%v", data.name, sig, parts[2])
}
}
}
}

View File

@ -1,7 +1,7 @@
package jwt_test package jwt_test
import ( import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"io/ioutil" "io/ioutil"
"strings" "strings"
"testing" "testing"
@ -142,3 +142,33 @@ func TestRSAKeyParsing(t *testing.T) {
} }
} }
func BenchmarkRS256Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
b.Fatal(err)
}
benchmarkSigning(b, jwt.SigningMethodRS256, parsedKey)
}
func BenchmarkRS384Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
b.Fatal(err)
}
benchmarkSigning(b, jwt.SigningMethodRS384, parsedKey)
}
func BenchmarkRS512Signing(b *testing.B) {
key, _ := ioutil.ReadFile("test/sample_key")
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
b.Fatal(err)
}
benchmarkSigning(b, jwt.SigningMethodRS512, parsedKey)
}

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAh5qA3rmqQQuu0vbKV/+zouz/y/Iy2pLpIcWUSyImSwoAoGCCqGSM49
AwEHoUQDQgAEYD54V/vp+54P9DXarYqx4MPcm+HKRIQzNasYSoRQHQ/6S6Ps8tpM
cT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYD54V/vp+54P9DXarYqx4MPcm+HK
RIQzNasYSoRQHQ/6S6Ps8tpMcT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg==
-----END PUBLIC KEY-----

View File

@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCaCvMHKhcG/qT7xsNLYnDT7sE/D+TtWIol1ROdaK1a564vx5pHbsRy
SEKcIxISi1igBwYFK4EEACKhZANiAATYa7rJaU7feLMqrAx6adZFNQOpaUH/Uylb
ZLriOLON5YFVwtVUpO1FfEXZUIQpptRPtc5ixIPY658yhBSb6irfIJUSP9aYTflJ
GKk/mDkK4t8mWBzhiD5B6jg9cEGhGgA=
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2Gu6yWlO33izKqwMemnWRTUDqWlB/1Mp
W2S64jizjeWBVcLVVKTtRXxF2VCEKabUT7XOYsSD2OufMoQUm+oq3yCVEj/WmE35
SRipP5g5CuLfJlgc4Yg+Qeo4PXBBoRoA
-----END PUBLIC KEY-----

View File

@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIB0pE4uFaWRx7t03BsYlYvF1YvKaBGyvoakxnodm9ou0R9wC+sJAjH
QZZJikOg4SwNqgQ/hyrOuDK2oAVHhgVGcYmgBwYFK4EEACOhgYkDgYYABAAJXIuw
12MUzpHggia9POBFYXSxaOGKGbMjIyDI+6q7wi7LMw3HgbaOmgIqFG72o8JBQwYN
4IbXHf+f86CRY1AA2wHzbHvt6IhkCXTNxBEffa1yMUgu8n9cKKF2iLgyQKcKqW33
8fGOw/n3Rm2Yd/EB56u2rnD29qS+nOM9eGS+gy39OQ==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQACVyLsNdjFM6R4IImvTzgRWF0sWjh
ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7
7eiIZAl0zcQRH32tcjFILvJ/XCihdoi4MkCnCqlt9/HxjsP590ZtmHfxAeertq5w
9vakvpzjPXhkvoMt/Tk=
-----END PUBLIC KEY-----

View File

@ -11,7 +11,6 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs" "github.com/drone/drone/Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs"
"github.com/drone/drone/pkg/remote" "github.com/drone/drone/pkg/remote"
"github.com/drone/drone/pkg/server" "github.com/drone/drone/pkg/server"
"github.com/drone/drone/pkg/server/session"
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus" log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
eventbus "github.com/drone/drone/pkg/bus/builtin" eventbus "github.com/drone/drone/pkg/bus/builtin"
@ -40,11 +39,6 @@ var conf = struct {
key string key string
} }
session struct {
expiry string
secret string
}
docker struct { docker struct {
host string host string
cert string cert string
@ -76,8 +70,6 @@ func main() {
flag.StringVar(&conf.server.addr, "server-addr", ":8080", "") flag.StringVar(&conf.server.addr, "server-addr", ":8080", "")
flag.StringVar(&conf.server.cert, "server-cert", "", "") flag.StringVar(&conf.server.cert, "server-cert", "", "")
flag.StringVar(&conf.server.key, "server-key", "", "") flag.StringVar(&conf.server.key, "server-key", "", "")
flag.StringVar(&conf.session.expiry, "session-expiry", "", "")
flag.StringVar(&conf.session.secret, "session-secret", "", "")
flag.StringVar(&conf.remote.driver, "remote-driver", "github", "") flag.StringVar(&conf.remote.driver, "remote-driver", "github", "")
flag.StringVar(&conf.remote.config, "remote-config", "https://github.com", "") flag.StringVar(&conf.remote.config, "remote-config", "https://github.com", "")
flag.StringVar(&conf.database.driver, "database-driver", "sqlite3", "") flag.StringVar(&conf.database.driver, "database-driver", "sqlite3", "")
@ -98,7 +90,6 @@ func main() {
panic(err) panic(err)
} }
session := session.New(conf.remote.config)
eventbus_ := eventbus.New() eventbus_ := eventbus.New()
queue_ := queue.New() queue_ := queue.New()
updater := runner.NewUpdater(eventbus_, store, remote) updater := runner.NewUpdater(eventbus_, store, remote)
@ -116,8 +107,7 @@ func main() {
api.Use(server.SetDatastore(store)) api.Use(server.SetDatastore(store))
api.Use(server.SetRemote(remote)) api.Use(server.SetRemote(remote))
api.Use(server.SetQueue(queue_)) api.Use(server.SetQueue(queue_))
api.Use(server.SetSession(session)) api.Use(server.SetUser())
api.Use(server.SetUser(session))
api.Use(server.SetRunner(&runner_)) api.Use(server.SetRunner(&runner_))
api.OPTIONS("/*path", func(c *gin.Context) {}) api.OPTIONS("/*path", func(c *gin.Context) {})
@ -129,9 +119,7 @@ func main() {
user.PATCH("", server.PutUserCurr) user.PATCH("", server.PutUserCurr)
user.GET("/feed", server.GetUserFeed) user.GET("/feed", server.GetUserFeed)
user.GET("/repos", server.GetUserRepos) user.GET("/repos", server.GetUserRepos)
user.GET("/tokens", server.GetUserTokens) user.POST("/token", server.PostUserToken)
user.POST("/tokens", server.PostToken)
user.DELETE("/tokens/:label", server.DelToken)
} }
users := api.Group("/users") users := api.Group("/users")
@ -199,7 +187,6 @@ func main() {
auth.Use(server.SetHeaders()) auth.Use(server.SetHeaders())
auth.Use(server.SetDatastore(store)) auth.Use(server.SetDatastore(store))
auth.Use(server.SetRemote(remote)) auth.Use(server.SetRemote(remote))
auth.Use(server.SetSession(session))
auth.GET("", server.GetLogin) auth.GET("", server.GetLogin)
auth.POST("", server.GetLogin) auth.POST("", server.GetLogin)
} }

View File

@ -1,12 +0,0 @@
package hash
import (
"crypto/sha256"
"encoding/hex"
)
func New(text, salt string) string {
hasher := sha256.New()
hasher.Write([]byte(text + salt))
return hex.EncodeToString(hasher.Sum(nil))
}

View File

@ -11,9 +11,9 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client" "github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client"
"github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru" "github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru"
"github.com/drone/drone/pkg/hash"
"github.com/drone/drone/pkg/oauth2" "github.com/drone/drone/pkg/oauth2"
"github.com/drone/drone/pkg/remote" "github.com/drone/drone/pkg/remote"
"github.com/drone/drone/pkg/token"
common "github.com/drone/drone/pkg/types" common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil" "github.com/drone/drone/pkg/utils/httputil"
) )
@ -186,17 +186,18 @@ func (g *Gitlab) Netrc(u *common.User, r *common.Repo) (*common.Netrc, error) {
return nil, err return nil, err
} }
netrc := &common.Netrc{} netrc := &common.Netrc{}
netrc.Machine = url_.Host
switch g.CloneMode { switch g.CloneMode {
case "oauth": case "oauth":
netrc.Login = "oauth2" netrc.Login = "oauth2"
netrc.Password = u.Token netrc.Password = u.Token
case "token": case "token":
t := token.New(token.HookToken, r.FullName)
netrc.Login = "drone-ci-token" netrc.Login = "drone-ci-token"
netrc.Password = hash.New(r.FullName, r.Hash) netrc.Password, err = t.Sign(r.Hash)
} }
netrc.Machine = url_.Host return netrc, err
return netrc, nil
} }
// Activate activates a repository by adding a Post-commit hook and // Activate activates a repository by adding a Post-commit hook and

View File

@ -6,7 +6,7 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/pkg/hash" "github.com/drone/drone/pkg/token"
) )
// RedirectSha accepts a request to retvie a redirect // RedirectSha accepts a request to retvie a redirect
@ -79,8 +79,14 @@ func GetPullRequest(c *gin.Context) {
store := ToDatastore(c) store := ToDatastore(c)
repo := ToRepo(c) repo := ToRepo(c)
// get the token and verify the hook is authorized parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
if c.Request.FormValue("access_token") != hash.New(repo.FullName, repo.Hash) { return repo.Hash, nil
})
if err != nil {
c.Fail(400, err)
return
}
if parsed.Text != repo.FullName {
c.AbortWithStatus(403) c.AbortWithStatus(403)
return return
} }
@ -118,8 +124,14 @@ func GetCommit(c *gin.Context) {
repo := ToRepo(c) repo := ToRepo(c)
sha := c.Params.ByName("sha") sha := c.Params.ByName("sha")
// get the token and verify the hook is authorized parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
if c.Request.FormValue("access_token") != hash.New(repo.FullName, repo.Hash) { return repo.Hash, nil
})
if err != nil {
c.Fail(400, err)
return
}
if parsed.Text != repo.FullName {
c.AbortWithStatus(403) c.AbortWithStatus(403)
return return
} }

View File

@ -6,8 +6,8 @@ import (
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus" log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/pkg/hash"
"github.com/drone/drone/pkg/queue" "github.com/drone/drone/pkg/queue"
"github.com/drone/drone/pkg/token"
common "github.com/drone/drone/pkg/types" common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil" "github.com/drone/drone/pkg/utils/httputil"
"github.com/drone/drone/pkg/yaml" "github.com/drone/drone/pkg/yaml"
@ -56,8 +56,16 @@ func PostHook(c *gin.Context) {
} }
// get the token and verify the hook is authorized // get the token and verify the hook is authorized
if c.Request.FormValue("access_token") != hash.New(repo.FullName, repo.Hash) { parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
log.Errorf("invalid token sent with hook.") return repo.Hash, nil
})
if err != nil {
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
c.Fail(400, err)
return
}
if parsed.Text != repo.FullName {
log.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
c.AbortWithStatus(403) c.AbortWithStatus(403)
return return
} }

View File

@ -7,7 +7,8 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar" "github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus" log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
common "github.com/drone/drone/pkg/types" "github.com/drone/drone/pkg/token"
"github.com/drone/drone/pkg/types"
) )
// GetLogin accepts a request to authorize the user and to // GetLogin accepts a request to authorize the user and to
@ -17,7 +18,6 @@ import (
// GET /authorize // GET /authorize
// //
func GetLogin(c *gin.Context) { func GetLogin(c *gin.Context) {
session := ToSession(c)
remote := ToRemote(c) remote := ToRemote(c)
store := ToDatastore(c) store := ToDatastore(c)
@ -65,13 +65,13 @@ func GetLogin(c *gin.Context) {
} }
// create the user account // create the user account
u = &common.User{} u = &types.User{}
u.Login = login.Login u.Login = login.Login
u.Token = login.Token u.Token = login.Token
u.Secret = login.Secret u.Secret = login.Secret
u.Email = login.Email u.Email = login.Email
u.Avatar = login.Avatar u.Avatar = login.Avatar
u.Hash = common.GenerateToken() u.Hash = types.GenerateToken()
// insert the user into the database // insert the user into the database
if err := store.AddUser(u); err != nil { if err := store.AddUser(u); err != nil {
@ -106,12 +106,9 @@ func GetLogin(c *gin.Context) {
return return
} }
token := &common.Token{ exp := time.Now().Add(time.Hour * 72).Unix()
Kind: common.TokenSess, token := token.New(token.SessToken, u.Login)
Login: u.Login, tokenstr, err := token.SignExpires(u.Hash, exp)
Issued: time.Now().UTC().Unix(),
}
tokenstr, err := session.GenerateToken(token)
if err != nil { if err != nil {
log.Errorf("cannot create token for %s. %s", u.Login, err) log.Errorf("cannot create token for %s. %s", u.Login, err)
c.Redirect(303, "/login#error=internal_error") c.Redirect(303, "/login#error=internal_error")

View File

@ -10,8 +10,8 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
"github.com/drone/drone/Godeps/_workspace/src/gopkg.in/yaml.v2" "github.com/drone/drone/Godeps/_workspace/src/gopkg.in/yaml.v2"
"github.com/drone/drone/pkg/hash"
"github.com/drone/drone/pkg/remote" "github.com/drone/drone/pkg/remote"
"github.com/drone/drone/pkg/token"
common "github.com/drone/drone/pkg/types" common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil" "github.com/drone/drone/pkg/utils/httputil"
"github.com/drone/drone/pkg/utils/sshutil" "github.com/drone/drone/pkg/utils/sshutil"
@ -208,10 +208,18 @@ func PostRepo(c *gin.Context) {
r.FullName, r.FullName,
) )
// crates the jwt token used to verify the repository
t := token.New(token.HookToken, r.FullName)
sig, err := t.Sign(r.Hash)
if err != nil {
c.Fail(500, err)
return
}
link := fmt.Sprintf( link := fmt.Sprintf(
"%s/api/hook?access_token=%s", "%s/api/hook?access_token=%s",
httputil.GetURL(c.Request), httputil.GetURL(c.Request),
hash.New(r.FullName, r.Hash), sig,
) )
// generate an RSA key and add to the repo // generate an RSA key and add to the repo

View File

@ -10,8 +10,8 @@ import (
"github.com/drone/drone/pkg/queue" "github.com/drone/drone/pkg/queue"
"github.com/drone/drone/pkg/remote" "github.com/drone/drone/pkg/remote"
"github.com/drone/drone/pkg/runner" "github.com/drone/drone/pkg/runner"
"github.com/drone/drone/pkg/server/session"
"github.com/drone/drone/pkg/store" "github.com/drone/drone/pkg/store"
"github.com/drone/drone/pkg/token"
common "github.com/drone/drone/pkg/types" common "github.com/drone/drone/pkg/types"
) )
@ -103,10 +103,6 @@ func ToDatastore(c *gin.Context) store.Store {
return c.MustGet("datastore").(store.Store) return c.MustGet("datastore").(store.Store)
} }
func ToSession(c *gin.Context) session.Session {
return c.MustGet("session").(session.Session)
}
func SetDatastore(ds store.Store) gin.HandlerFunc { func SetDatastore(ds store.Store) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
c.Set("datastore", ds) c.Set("datastore", ds)
@ -114,44 +110,24 @@ func SetDatastore(ds store.Store) gin.HandlerFunc {
} }
} }
func SetSession(s session.Session) gin.HandlerFunc { func SetUser() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
c.Set("session", s)
c.Next()
}
}
func SetUser(s session.Session) gin.HandlerFunc { var store = ToDatastore(c)
return func(c *gin.Context) { var user *common.User
ds := ToDatastore(c)
token := s.GetLogin(c.Request)
if token == nil || len(token.Login) == 0 {
c.Next()
return
}
user, err := ds.UserLogin(token.Login) _, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
if err == nil { var err error
user, err = store.UserLogin(t.Text)
if err != nil {
return "", err
}
return user.Hash, nil
})
if err == nil && user != nil && user.ID != 0 {
c.Set("user", user) c.Set("user", user)
} }
// if session token we can proceed, otherwise
// we should validate the token hasn't been revoked
switch token.Kind {
case common.TokenSess:
c.Next()
return
}
// to verify the token we fetch from the datastore
// and check to see if the token issued date matches
// what we found in the jwt (in case the label is re-used)
t, err := ds.TokenLabel(user, token.Label)
if err != nil || t.Issued != token.Issued {
c.AbortWithStatus(403)
return
}
c.Next() c.Next()
} }
} }

View File

@ -1,107 +0,0 @@
package session
import (
"fmt"
"net/http"
"time"
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go"
common "github.com/drone/drone/pkg/types"
)
type Session interface {
GenerateToken(*common.Token) (string, error)
GetLogin(*http.Request) *common.Token
}
type session struct {
secret []byte
expire time.Duration
}
func New(rand string) Session {
secret := []byte(rand)
expire := time.Hour * 72
return &session{
secret: secret,
expire: expire,
}
}
// GenerateToken generates a JWT token for the user session
// that can be appended to the #access_token segment to
// facilitate client-based OAuth2.
func (s *session) GenerateToken(t *common.Token) (string, error) {
token := jwt.New(jwt.GetSigningMethod("HS256"))
token.Claims["user"] = t.Login
token.Claims["kind"] = t.Kind
token.Claims["date"] = t.Issued
token.Claims["label"] = t.Label
return token.SignedString(s.secret)
}
// GetLogin gets the currently authenticated user for the
// http.Request. The user details will be stored as either
// a simple API token or JWT bearer token.
func (s *session) GetLogin(r *http.Request) *common.Token {
t := getToken(r)
if len(t) == 0 {
return nil
}
claims := getClaims(t, s.secret)
if claims == nil || claims["user"] == nil || claims["date"] == nil || claims["label"] == nil || claims["kind"] == nil {
return nil
}
token := &common.Token{
Kind: claims["kind"].(string),
Login: claims["user"].(string),
Label: claims["label"].(string),
Issued: int64(claims["date"].(float64)),
}
if token.Kind != common.TokenSess {
return token
}
if time.Unix(token.Issued, 0).Add(s.expire).Before(time.Now()) {
return nil
}
return token
}
// getToken is a helper function that extracts the token
// from the http.Request.
func getToken(r *http.Request) string {
token := getTokenHeader(r)
if len(token) == 0 {
token = getTokenParam(r)
}
return token
}
// getTokenHeader parses the JWT token value from
// the http Authorization header.
func getTokenHeader(r *http.Request) string {
var tokenstr = r.Header.Get("Authorization")
fmt.Sscanf(tokenstr, "Bearer %s", &tokenstr)
return tokenstr
}
// getTokenParam parses the JWT token value from
// the http Request's query parameter.
func getTokenParam(r *http.Request) string {
return r.FormValue("access_token")
}
// getClaims is a helper function that extracts the token
// claims from the JWT token string.
func getClaims(token string, secret []byte) map[string]interface{} {
t, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
return secret, nil
})
if err != nil || !t.Valid {
return nil
}
return t.Claims
}

View File

@ -1,68 +0,0 @@
package server
import (
"time"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
common "github.com/drone/drone/pkg/types"
)
// POST /api/user/tokens
func PostToken(c *gin.Context) {
sess := ToSession(c)
store := ToDatastore(c)
user := ToUser(c)
in := &common.Token{}
if !c.BindWith(in, binding.JSON) {
return
}
token := &common.Token{}
token.Label = in.Label
token.UserID = user.ID
// token.Repos = in.Repos
// token.Scopes = in.Scopes
token.Login = user.Login
token.Kind = common.TokenUser
token.Issued = time.Now().UTC().Unix()
err := store.AddToken(token)
if err != nil {
c.Fail(500, err)
return
}
jwt, err := sess.GenerateToken(token)
if err != nil {
c.Fail(400, err)
return
}
c.JSON(200, struct {
*common.Token
Hash string `json:"hash"`
}{token, jwt})
}
// DELETE /api/user/tokens/:label
func DelToken(c *gin.Context) {
store := ToDatastore(c)
user := ToUser(c)
label := c.Params.ByName("label")
token, err := store.TokenLabel(user, label)
if err != nil {
c.Fail(404, err)
return
}
err = store.DelToken(token)
if err != nil {
c.Fail(400, err)
return
}
c.Writer.WriteHeader(200)
}

View File

@ -1,120 +0,0 @@
package server
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go"
. "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/stretchr/testify/mock"
"github.com/drone/drone/pkg/server/recorder"
"github.com/drone/drone/pkg/server/session"
"github.com/drone/drone/pkg/store/mock"
"github.com/drone/drone/pkg/types"
)
var createTests = []struct {
inLabel string
inBody string
storeErr error
outCode int
outKind string
}{
{"", `{}`, sql.ErrNoRows, 500, ""},
{"app1", `{"label": "app1"}`, nil, 200, types.TokenUser},
{"app2", `{"label": "app2"}`, nil, 200, types.TokenUser},
}
var deleteTests = []struct {
inLabel string
errTokenLabel error
errDelToken error
outCode int
outToken *types.Token
}{
{"app1", sql.ErrNoRows, nil, 404, &types.Token{}},
{"app2", nil, sql.ErrNoRows, 400, &types.Token{Label: "app2"}},
{"app3", nil, nil, 200, &types.Token{Label: "app2"}},
}
func TestToken(t *testing.T) {
store := new(mocks.Store)
g := Goblin(t)
g.Describe("Token", func() {
// POST /api/user/tokens
g.It("should create tokens", func() {
for _, test := range createTests {
rw := recorder.New()
ctx := gin.Context{Engine: gin.Default(), Writer: rw}
body := bytes.NewBufferString(test.inBody)
ctx.Request, _ = http.NewRequest("POST", "/api/user/tokens", body)
ctx.Set("datastore", store)
ctx.Set("user", &types.User{Login: "Freya"})
ctx.Set("session", session.New("Otto"))
// prepare the mock
store.On("AddToken", mock.AnythingOfType("*types.Token")).Return(test.storeErr).Once()
PostToken(&ctx)
g.Assert(rw.Code).Equal(test.outCode)
if test.outCode != 200 {
continue
}
var respjson map[string]interface{}
json.Unmarshal(rw.Body.Bytes(), &respjson)
g.Assert(respjson["kind"]).Equal(types.TokenUser)
g.Assert(respjson["label"]).Equal(test.inLabel)
// this is probably going too far... maybe just validate hash is not empty?
jwt.Parse(respjson["hash"].(string), func(token *jwt.Token) (interface{}, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
g.Assert(ok).IsTrue()
g.Assert(token.Claims["label"]).Equal(test.inLabel)
return nil, nil
})
}
})
// DELETE /api/user/tokens/:label
g.It("should delete tokens", func() {
for _, test := range deleteTests {
rw := recorder.New()
ctx := gin.Context{Engine: gin.Default(), Writer: rw}
ctx.Params = append(ctx.Params, gin.Param{Key: "label", Value: test.inLabel})
ctx.Set("datastore", store)
ctx.Set("user", &types.User{Login: "Freya"})
ctx.Set("session", session.New("Otto"))
// prepare the mock
store.On("TokenLabel", mock.AnythingOfType("*types.User"), test.inLabel).Return(test.outToken, test.errTokenLabel).Once()
if test.errTokenLabel == nil {
// we don't need this expectation if we error on our first
store.On("DelToken", mock.AnythingOfType("*types.Token")).Return(test.errDelToken).Once()
}
fmt.Println(test)
DelToken(&ctx)
g.Assert(rw.Code).Equal(test.outCode)
if test.outCode != 200 {
continue
}
var respjson map[string]interface{}
json.Unmarshal(rw.Body.Bytes(), &respjson)
fmt.Println(rw.Code, respjson)
}
})
})
}

View File

@ -5,7 +5,8 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar" "github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
common "github.com/drone/drone/pkg/types" "github.com/drone/drone/pkg/token"
"github.com/drone/drone/pkg/types"
) )
// GetUserCurr accepts a request to retrieve the // GetUserCurr accepts a request to retrieve the
@ -27,7 +28,7 @@ func PutUserCurr(c *gin.Context) {
store := ToDatastore(c) store := ToDatastore(c)
user := ToUser(c) user := ToUser(c)
in := &common.User{} in := &types.User{}
if !c.BindWith(in, binding.JSON) { if !c.BindWith(in, binding.JSON) {
return return
} }
@ -76,19 +77,15 @@ func GetUserFeed(c *gin.Context) {
} }
} }
// GetUserTokens accepts a request to get the currently // POST /api/user/token
// authenticated user's token list from the datastore, func PostUserToken(c *gin.Context) {
// encoded and returned in JSON format.
//
// GET /api/user/tokens
//
func GetUserTokens(c *gin.Context) {
store := ToDatastore(c)
user := ToUser(c) user := ToUser(c)
tokens, err := store.TokenList(user)
t := token.New(token.UserToken, user.Login)
s, err := t.Sign(user.Hash)
if err != nil { if err != nil {
c.Fail(400, err) c.Fail(500, err)
} else { } else {
c.JSON(200, &tokens) c.String(200, "application/jwt", s)
} }
} }

View File

@ -5,7 +5,7 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar" "github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
common "github.com/drone/drone/pkg/types" "github.com/drone/drone/pkg/types"
) )
// GetUsers accepts a request to retrieve all users // GetUsers accepts a request to retrieve all users
@ -32,9 +32,13 @@ func GetUsers(c *gin.Context) {
func PostUser(c *gin.Context) { func PostUser(c *gin.Context) {
store := ToDatastore(c) store := ToDatastore(c)
name := c.Params.ByName("name") name := c.Params.ByName("name")
user := &common.User{Login: name} user := &types.User{Login: name}
user.Token = c.Request.FormValue("token") user.Token = c.Request.FormValue("token")
user.Secret = c.Request.FormValue("secret") user.Secret = c.Request.FormValue("secret")
user.Hash = c.Request.FormValue("hash")
if len(user.Hash) == 0 {
user.Hash = types.GenerateToken()
}
if err := store.AddUser(user); err != nil { if err := store.AddUser(user); err != nil {
c.Fail(400, err) c.Fail(400, err)
} else { } else {
@ -75,7 +79,7 @@ func PutUser(c *gin.Context) {
return return
} }
in := &common.User{} in := &types.User{}
if !c.BindWith(in, binding.JSON) { if !c.BindWith(in, binding.JSON) {
return return
} }

View File

@ -8,10 +8,10 @@ import (
"github.com/drone/drone/pkg/store/builtin/migrate" "github.com/drone/drone/pkg/store/builtin/migrate"
"github.com/drone/drone/Godeps/_workspace/src/github.com/BurntSushi/migration" "github.com/drone/drone/Godeps/_workspace/src/github.com/BurntSushi/migration"
_ "github.com/drone/drone/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
_ "github.com/drone/drone/Godeps/_workspace/src/github.com/lib/pq" _ "github.com/drone/drone/Godeps/_workspace/src/github.com/lib/pq"
_ "github.com/drone/drone/Godeps/_workspace/src/github.com/mattn/go-sqlite3" _ "github.com/drone/drone/Godeps/_workspace/src/github.com/mattn/go-sqlite3"
"github.com/drone/drone/Godeps/_workspace/src/github.com/russross/meddler" "github.com/drone/drone/Godeps/_workspace/src/github.com/russross/meddler"
_ "github.com/drone/drone/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
) )
const ( const (
@ -94,7 +94,6 @@ func New(db *sql.DB) store.Store {
*Jobstore *Jobstore
*Blobstore *Blobstore
*Starstore *Starstore
*Tokenstore
*Agentstore *Agentstore
}{ }{
NewUserstore(db), NewUserstore(db),
@ -103,7 +102,6 @@ func New(db *sql.DB) store.Store {
NewJobstore(db), NewJobstore(db),
NewBlobstore(db), NewBlobstore(db),
NewStarstore(db), NewStarstore(db),
NewTokenstore(db),
NewAgentstore(db), NewAgentstore(db),
} }
} }

View File

@ -1,43 +0,0 @@
package builtin
import (
"database/sql"
"github.com/drone/drone/pkg/types"
)
type Tokenstore struct {
*sql.DB
}
func NewTokenstore(db *sql.DB) *Tokenstore {
return &Tokenstore{db}
}
// Token returns a token by ID.
func (db *Tokenstore) Token(id int64) (*types.Token, error) {
return getToken(db, rebind(stmtTokenSelect), id)
}
// TokenLabel returns a token by label
func (db *Tokenstore) TokenLabel(user *types.User, label string) (*types.Token, error) {
return getToken(db, rebind(stmtTokenSelectTokenUserLabel), user.ID, label)
}
// TokenList returns a list of all user tokens.
func (db *Tokenstore) TokenList(user *types.User) ([]*types.Token, error) {
return getTokens(db, rebind(stmtTokenSelectTokenUserId), user.ID)
}
// AddToken inserts a new token into the datastore.
// If the token label already exists for the user
// an error is returned.
func (db *Tokenstore) AddToken(token *types.Token) error {
return createToken(db, rebind(stmtTokenInsert), token)
}
// DelToken removes the DelToken from the datastore.
func (db *Tokenstore) DelToken(token *types.Token) error {
var _, err = db.Exec(rebind(stmtTokenDelete), token.ID)
return err
}

View File

@ -1,255 +0,0 @@
package builtin
// DO NOT EDIT
// code generated by go:generate
import (
"database/sql"
"encoding/json"
. "github.com/drone/drone/pkg/types"
)
var _ = json.Marshal
// generic database interface, matching both *sql.Db and *sql.Tx
type tokenDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
func getToken(db tokenDB, query string, args ...interface{}) (*Token, error) {
row := db.QueryRow(query, args...)
return scanToken(row)
}
func getTokens(db tokenDB, query string, args ...interface{}) ([]*Token, error) {
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
return scanTokens(rows)
}
func createToken(db tokenDB, query string, v *Token) error {
var v0 int64
var v1 string
var v2 string
var v3 int64
var v4 int64
v0 = v.UserID
v1 = v.Kind
v2 = v.Label
v3 = v.Expiry
v4 = v.Issued
res, err := db.Exec(query,
&v0,
&v1,
&v2,
&v3,
&v4,
)
if err != nil {
return err
}
v.ID, err = res.LastInsertId()
return err
}
func updateToken(db tokenDB, query string, v *Token) error {
var v0 int64
var v1 int64
var v2 string
var v3 string
var v4 int64
var v5 int64
v0 = v.ID
v1 = v.UserID
v2 = v.Kind
v3 = v.Label
v4 = v.Expiry
v5 = v.Issued
_, err := db.Exec(query,
&v1,
&v2,
&v3,
&v4,
&v5,
&v0,
)
return err
}
func scanToken(row *sql.Row) (*Token, error) {
var v0 int64
var v1 int64
var v2 string
var v3 string
var v4 int64
var v5 int64
err := row.Scan(
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
)
if err != nil {
return nil, err
}
v := &Token{}
v.ID = v0
v.UserID = v1
v.Kind = v2
v.Label = v3
v.Expiry = v4
v.Issued = v5
return v, nil
}
func scanTokens(rows *sql.Rows) ([]*Token, error) {
var err error
var vv []*Token
for rows.Next() {
var v0 int64
var v1 int64
var v2 string
var v3 string
var v4 int64
var v5 int64
err = rows.Scan(
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
)
if err != nil {
return vv, err
}
v := &Token{}
v.ID = v0
v.UserID = v1
v.Kind = v2
v.Label = v3
v.Expiry = v4
v.Issued = v5
vv = append(vv, v)
}
return vv, rows.Err()
}
const stmtTokenSelectList = `
SELECT
token_id
,token_user_id
,token_kind
,token_label
,token_expiry
,token_issued
FROM tokens
`
const stmtTokenSelectRange = `
SELECT
token_id
,token_user_id
,token_kind
,token_label
,token_expiry
,token_issued
FROM tokens
LIMIT ? OFFSET ?
`
const stmtTokenSelect = `
SELECT
token_id
,token_user_id
,token_kind
,token_label
,token_expiry
,token_issued
FROM tokens
WHERE token_id = ?
`
const stmtTokenSelectTokenUserId = `
SELECT
token_id
,token_user_id
,token_kind
,token_label
,token_expiry
,token_issued
FROM tokens
WHERE token_user_id = ?
`
const stmtTokenSelectTokenUserLabel = `
SELECT
token_id
,token_user_id
,token_kind
,token_label
,token_expiry
,token_issued
FROM tokens
WHERE token_user_id = ?
AND token_label = ?
`
const stmtTokenInsert = `
INSERT INTO tokens (
token_user_id
,token_kind
,token_label
,token_expiry
,token_issued
) VALUES (?,?,?,?,?);
`
const stmtTokenUpdate = `
UPDATE tokens SET
token_user_id = ?
,token_kind = ?
,token_label = ?
,token_expiry = ?
,token_issued = ?
WHERE token_id = ?
`
const stmtTokenDelete = `
DELETE FROM tokens
WHERE token_id = ?
`
const stmtTokenTable = `
CREATE TABLE IF NOT EXISTS tokens (
token_id INTEGER PRIMARY KEY AUTOINCREMENT
,token_user_id INTEGER
,token_kind VARCHAR
,token_label VARCHAR
,token_expiry INTEGER
,token_issued INTEGER
);
`
const stmtTokenTokenUserIdIndex = `
CREATE INDEX IF NOT EXISTS ix_token_user_id ON tokens (token_user_id);
`
const stmtTokenTokenUserLabelIndex = `
CREATE UNIQUE INDEX IF NOT EXISTS ux_token_user_label ON tokens (token_user_id,token_label);
`

View File

@ -1,149 +0,0 @@
package builtin
import (
"testing"
"time"
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
"github.com/drone/drone/pkg/types"
)
func TestTokenstore(t *testing.T) {
db := mustConnectTest()
ts := NewTokenstore(db)
defer db.Close()
g := goblin.Goblin(t)
g.Describe("Tokenstore", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec("DELETE FROM tokens")
})
g.It("Should Add a new Token", func() {
token := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
err := ts.AddToken(&token)
g.Assert(err == nil).IsTrue()
g.Assert(token.ID != 0).IsTrue()
})
g.It("Should get a Token", func() {
token := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
err1 := ts.AddToken(&token)
gettoken, err2 := ts.Token(token.ID)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(token.ID).Equal(gettoken.ID)
g.Assert(token.Label).Equal(gettoken.Label)
g.Assert(token.Kind).Equal(gettoken.Kind)
g.Assert(token.Issued).Equal(gettoken.Issued)
g.Assert(token.Expiry).Equal(gettoken.Expiry)
})
g.It("Should Get a Token By Label", func() {
token := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
err1 := ts.AddToken(&token)
gettoken, err2 := ts.TokenLabel(&types.User{ID: 1}, "foo")
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(token.ID).Equal(gettoken.ID)
g.Assert(token.Label).Equal(gettoken.Label)
g.Assert(token.Kind).Equal(gettoken.Kind)
g.Assert(token.Issued).Equal(gettoken.Issued)
g.Assert(token.Expiry).Equal(gettoken.Expiry)
})
g.It("Should Enforce Unique Token Label", func() {
token1 := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
token2 := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
err1 := ts.AddToken(&token1)
err2 := ts.AddToken(&token2)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsFalse()
})
g.It("Should Get a User Token List", func() {
token1 := types.Token{
UserID: 1,
Label: "bar",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
token2 := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
token3 := types.Token{
UserID: 2,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
ts.AddToken(&token1)
ts.AddToken(&token2)
ts.AddToken(&token3)
tokens, err := ts.TokenList(&types.User{ID: 1})
g.Assert(err == nil).IsTrue()
g.Assert(len(tokens)).Equal(2)
g.Assert(tokens[0].ID).Equal(token1.ID)
g.Assert(tokens[0].Label).Equal(token1.Label)
g.Assert(tokens[0].Kind).Equal(token1.Kind)
g.Assert(tokens[0].Issued).Equal(token1.Issued)
g.Assert(tokens[0].Expiry).Equal(token1.Expiry)
})
g.It("Should Del a Token", func() {
token := types.Token{
UserID: 1,
Label: "foo",
Kind: types.TokenUser,
Issued: time.Now().Unix(),
Expiry: time.Now().Unix() + 1000,
}
ts.AddToken(&token)
_, err1 := ts.Token(token.ID)
err2 := ts.DelToken(&token)
_, err3 := ts.Token(token.ID)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(err3 == nil).IsFalse()
})
})
}

View File

@ -84,53 +84,6 @@ func (m *Store) DelUser(_a0 *types.User) error {
return r0 return r0
} }
func (m *Store) Token(_a0 int64) (*types.Token, error) {
ret := m.Called(_a0)
var r0 *types.Token
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.Token)
}
r1 := ret.Error(1)
return r0, r1
}
func (m *Store) TokenLabel(_a0 *types.User, _a1 string) (*types.Token, error) {
ret := m.Called(_a0, _a1)
var r0 *types.Token
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.Token)
}
r1 := ret.Error(1)
return r0, r1
}
func (m *Store) TokenList(_a0 *types.User) ([]*types.Token, error) {
ret := m.Called(_a0)
var r0 []*types.Token
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*types.Token)
}
r1 := ret.Error(1)
return r0, r1
}
func (m *Store) AddToken(_a0 *types.Token) error {
ret := m.Called(_a0)
r0 := ret.Error(0)
return r0
}
func (m *Store) DelToken(_a0 *types.Token) error {
ret := m.Called(_a0)
r0 := ret.Error(0)
return r0
}
func (m *Store) Starred(_a0 *types.User, _a1 *types.Repo) (bool, error) { func (m *Store) Starred(_a0 *types.User, _a1 *types.Repo) (bool, error) {
ret := m.Called(_a0, _a1) ret := m.Called(_a0, _a1)

View File

@ -70,25 +70,6 @@ type Store interface {
// //
// Token returns a token by ID.
Token(int64) (*types.Token, error)
// TokenLabel returns a token by label
TokenLabel(*types.User, string) (*types.Token, error)
// TokenList returns a list of all user tokens.
TokenList(*types.User) ([]*types.Token, error)
// AddToken inserts a new token into the datastore.
// If the token label already exists for the user
// an error is returned.
AddToken(*types.Token) error
// DelToken removes the DelToken from the datastore.
DelToken(*types.Token) error
//
// Starred returns true if the user starred // Starred returns true if the user starred
// the given repository. // the given repository.
Starred(*types.User, *types.Repo) (bool, error) Starred(*types.User, *types.Repo) (bool, error)

98
pkg/token/token.go Normal file
View File

@ -0,0 +1,98 @@
package token
import (
"net/http"
"github.com/drone/drone/Godeps/_workspace/src/github.com/dgrijalva/jwt-go"
)
type SecretFunc func(*Token) (string, error)
const (
UserToken = "user"
SessToken = "sess"
HookToken = "hook"
)
// Default algorithm used to sign JWT tokens.
const SignerAlgo = "HS256"
type Token struct {
Kind string
Text string
}
// Parse parses
func Parse(raw string, fn SecretFunc) (*Token, error) {
token := &Token{}
parsed, err := jwt.Parse(raw, keyFunc(token, fn))
if err != nil {
return nil, err
} else if !parsed.Valid {
return nil, jwt.ValidationError{}
}
return token, nil
}
func ParseRequest(req *http.Request, fn SecretFunc) (*Token, error) {
token := &Token{}
parsed, err := jwt.ParseFromRequest(req, keyFunc(token, fn))
if err != nil {
return nil, err
} else if !parsed.Valid {
return nil, jwt.ValidationError{}
}
return token, nil
}
func New(kind, text string) *Token {
return &Token{Kind: kind, Text: text}
}
// Sign signs the token using the given secret hash
// and returns the string value.
func (t *Token) Sign(secret string) (string, error) {
return t.SignExpires(secret, 0)
}
// Sign signs the token using the given secret hash
// with an expiration date.
func (t *Token) SignExpires(secret string, exp int64) (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
token.Claims["type"] = t.Kind
token.Claims["text"] = t.Text
if exp > 0 {
token.Claims["exp"] = float64(exp)
}
return token.SignedString([]byte(secret))
}
func keyFunc(token *Token, fn SecretFunc) jwt.Keyfunc {
return func(t *jwt.Token) (interface{}, error) {
// validate the correct algorithm is being used
if t.Method.Alg() != SignerAlgo {
return nil, jwt.ErrSignatureInvalid
}
// extract the token kind and cast to
// the expected type.
kindv, ok := t.Claims["type"]
if !ok {
return nil, jwt.ValidationError{}
}
token.Kind, _ = kindv.(string)
// extract the token value and cast to
// exepected type.
textv, ok := t.Claims["text"]
if !ok {
return nil, jwt.ValidationError{}
}
token.Text, _ = textv.(string)
// invoke the callback function to retrieve
// the secret key used to verify
secret, err := fn(token)
return []byte(secret), err
}
}

1
pkg/token/token_test.go Normal file
View File

@ -0,0 +1 @@
package token

View File

@ -1,18 +0,0 @@
package types
type Token struct {
ID int64 `meddler:"token_id,pk" json:"-"`
UserID int64 `meddler:"token_user_id" json:"-" sql:"index:ix_token_user_id,unique:ux_token_user_label"`
Login string `meddler:"-" json:"-" sql:"-"`
Kind string `meddler:"token_kind" json:"kind,omitempty"`
Label string `meddler:"token_label" json:"label,omitempty" sql:"unique:ux_token_user_label"`
Expiry int64 `meddler:"token_expiry" json:"expiry,omitempty"`
Issued int64 `meddler:"token_issued" json:"issued_at,omitempty"`
}
const (
TokenUser = "u"
TokenSess = "s"
TokenHook = "h"
TokenAgent = "a"
)

View File

@ -1,18 +1,15 @@
package types package types
import ( import (
"crypto/md5"
"crypto/rand" "crypto/rand"
"fmt"
"io" "io"
"strings"
) )
// standard characters allowed in token string. // standard characters allowed in token string.
var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// default token length // default token length
var length = 40 var length = 32
// GenerateToken generates random strings good for use in URIs to // GenerateToken generates random strings good for use in URIs to
// identify unique objects. // identify unique objects.
@ -37,12 +34,3 @@ func GenerateToken() string {
} }
} }
} }
// helper function to create a Gravatar Hash
// for the given Email address.
func CreateGravatar(email string) string {
email = strings.ToLower(strings.TrimSpace(email))
hash := md5.New()
hash.Write([]byte(email))
return fmt.Sprintf("%x", hash.Sum(nil))
}

View File

@ -4,13 +4,6 @@ import (
"testing" "testing"
) )
func Test_CreateGravatar(t *testing.T) {
var got, want = CreateGravatar("dr_cooper@caltech.edu"), "2b77ba83e2216ddcd11fe8c24b70c2a3"
if got != want {
t.Errorf("Got gravatar hash %s, want %s", got, want)
}
}
func Test_GenerateToken(t *testing.T) { func Test_GenerateToken(t *testing.T) {
token := GenerateToken() token := GenerateToken()
if len(token) != length { if len(token) != length {