diff --git a/client/handler.go b/client/handler.go index b1a9afc9f9c..aa123d625f0 100644 --- a/client/handler.go +++ b/client/handler.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/julienschmidt/httprouter" - "github.com/ory-am/common/rand/sequence" + "github.com/ory/hydra/rand/sequence" "github.com/ory/herodot" "github.com/ory/hydra/firewall" "github.com/ory/ladon" diff --git a/client/manager_test.go b/client/manager_test.go index 26924d4427f..1b8b7c8c1b7 100644 --- a/client/manager_test.go +++ b/client/manager_test.go @@ -19,7 +19,7 @@ import ( "github.com/ory/hydra/pkg" "github.com/ory/ladon" "github.com/stretchr/testify/assert" - "gopkg.in/ory-am/dockertest.v3" + "github.com/ory/dockertest" ) var clientManagers = map[string]Storage{} diff --git a/cmd/token_user.go b/cmd/token_user.go index 7eab4f98ce3..cd85ea005dc 100644 --- a/cmd/token_user.go +++ b/cmd/token_user.go @@ -8,7 +8,7 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/ory-am/common/rand/sequence" + "github.com/ory/hydra/rand/sequence" "github.com/ory/hydra/pkg" "github.com/spf13/cobra" "github.com/toqueteos/webbrowser" diff --git a/glide.yaml b/glide.yaml index f64cce05da2..6f209eebc17 100644 --- a/glide.yaml +++ b/glide.yaml @@ -6,6 +6,8 @@ import: version: ~3.0.0 - package: github.com/go-sql-driver/mysql version: ~1.3.0 +- package: github.com/gorilla/context + version: ~1.1.0 - package: github.com/gorilla/sessions version: ~1.1.0 - package: github.com/imdario/mergo @@ -16,13 +18,10 @@ import: - package: github.com/lib/pq - package: github.com/meatballhat/negroni-logrus - package: github.com/moul/http2curl -- package: github.com/ory-am/common - version: ~0.4.0 - subpackages: - - pkg - - rand/sequence +- package: github.com/oleiade/reflections + version: ~1.0.0 - package: github.com/ory/fosite - version: ~0.9.0 + version: 0.9.0 subpackages: - compose - handler/oauth2 @@ -33,9 +32,9 @@ import: - package: github.com/ory/graceful version: ~0.1.0 - package: github.com/ory/herodot - version: ~0.0.3 + version: ~0.1.0 - package: github.com/ory/ladon - version: ~0.7.2 + version: 0.7.2 subpackages: - manager/memory - manager/sql @@ -44,7 +43,7 @@ import: - package: github.com/pkg/errors version: ~0.8.0 - package: github.com/pkg/profile - version: ~1.2.0 + version: ~1.2.1 - package: github.com/rubenv/sql-migrate - package: github.com/spf13/cobra - package: github.com/spf13/viper @@ -52,6 +51,11 @@ import: version: ~1.1.0 subpackages: - json +- package: github.com/stretchr/testify + version: ~1.1.4 + subpackages: + - assert + - require - package: github.com/toqueteos/webbrowser version: ~1.0.0 - package: github.com/urfave/negroni @@ -60,11 +64,7 @@ import: subpackages: - clientcredentials - package: gopkg.in/yaml.v2 -testImports: -- package: gopkg.in/ory-am/dockertest.v3 - version: ~3.0.7 -- package: github.com/stretchr/testify - version: ~1.1.4 - subpackages: - - assert - - require \ No newline at end of file +testImport: +- package: github.com/bmizerany/assert +- package: github.com/ory/dockertest + version: ~3.0.7 \ No newline at end of file diff --git a/integration/docker.go b/integration/docker.go index c612f385f0f..1af57eabb2f 100644 --- a/integration/docker.go +++ b/integration/docker.go @@ -8,7 +8,7 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" - "gopkg.in/ory-am/dockertest.v3" + "github.com/ory/dockertest" ) var resources []*dockertest.Resource diff --git a/jwk/generator_hs256.go b/jwk/generator_hs256.go index 878a61e086e..b00b622da54 100644 --- a/jwk/generator_hs256.go +++ b/jwk/generator_hs256.go @@ -3,7 +3,7 @@ package jwk import ( "crypto/x509" - "github.com/ory-am/common/rand/sequence" + "github.com/ory/hydra/rand/sequence" "github.com/pkg/errors" "github.com/square/go-jose" ) diff --git a/oauth2/equalKeys.go b/oauth2/equalKeys.go new file mode 100644 index 00000000000..f3affdef47e --- /dev/null +++ b/oauth2/equalKeys.go @@ -0,0 +1,49 @@ +package oauth2 + +import "testing" +import "github.com/oleiade/reflections" +import "github.com/stretchr/testify/assert" +import "github.com/stretchr/testify/require" + +func AssertObjectKeysEqual(t *testing.T, a, b interface{}, keys ...string) { + assert.True(t, len(keys) > 0, "No keys provided.") + for _, k := range keys { + c, err := reflections.GetField(a, k) + assert.Nil(t, err) + d, err := reflections.GetField(b, k) + assert.Nil(t, err) + assert.Equal(t, c, d, "%s", k) + } +} + +func AssertObjectKeysNotEqual(t *testing.T, a, b interface{}, keys ...string) { + assert.True(t, len(keys) > 0, "No keys provided.") + for _, k := range keys { + c, err := reflections.GetField(a, k) + assert.Nil(t, err) + d, err := reflections.GetField(b, k) + assert.Nil(t, err) + assert.NotEqual(t, c, d, "%s", k) + } +} + +func RequireObjectKeysEqual(t *testing.T, a, b interface{}, keys ...string) { + assert.True(t, len(keys) > 0, "No keys provided.") + for _, k := range keys { + c, err := reflections.GetField(a, k) + assert.Nil(t, err) + d, err := reflections.GetField(b, k) + assert.Nil(t, err) + require.Equal(t, c, d, "%s", k) + } +} +func RequireObjectKeysNotEqual(t *testing.T, a, b interface{}, keys ...string) { + assert.True(t, len(keys) > 0, "No keys provided.") + for _, k := range keys { + c, err := reflections.GetField(a, k) + assert.Nil(t, err) + d, err := reflections.GetField(b, k) + assert.Nil(t, err) + require.NotEqual(t, c, d, "%s", k) + } +} diff --git a/oauth2/equalKeys_test.go b/oauth2/equalKeys_test.go new file mode 100644 index 00000000000..bd1a3f2bdfe --- /dev/null +++ b/oauth2/equalKeys_test.go @@ -0,0 +1,17 @@ +package oauth2 + +import "testing" + +func TestAssertObjectsAreEqualByKeys(t *testing.T) { + type foo struct { + Name string + Body int + } + a := &foo{"foo", 1} + b := &foo{"bar", 1} + c := &foo{"baz", 3} + + AssertObjectKeysEqual(t, a, a, "Name", "Body") + AssertObjectKeysNotEqual(t, a, b, "Name") + AssertObjectKeysNotEqual(t, a, c, "Name", "Body") +} diff --git a/oauth2/fosite_store_test.go b/oauth2/fosite_store_test.go index 973d3052477..a409c6bcf4e 100644 --- a/oauth2/fosite_store_test.go +++ b/oauth2/fosite_store_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/Sirupsen/logrus" - c "github.com/ory-am/common/pkg" "github.com/ory/fosite" "github.com/ory/hydra/client" "github.com/ory/hydra/integration" @@ -84,7 +83,7 @@ func TestCreateGetDeleteAuthorizeCodes(t *testing.T) { res, err := m.GetAuthorizeCodeSession(ctx, "4321", &fosite.DefaultSession{}) require.Nil(t, err) - c.AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") + AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") err = m.DeleteAuthorizeCodeSession(ctx, "4321") require.Nil(t, err) @@ -109,7 +108,7 @@ func TestCreateGetDeleteAccessTokenSession(t *testing.T) { res, err := m.GetAccessTokenSession(ctx, "4321", &fosite.DefaultSession{}) require.Nil(t, err) - c.AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") + AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") err = m.DeleteAccessTokenSession(ctx, "4321") require.Nil(t, err) @@ -134,7 +133,7 @@ func TestCreateGetDeleteOpenIDConnectSession(t *testing.T) { res, err := m.GetOpenIDConnectSession(ctx, "4321", &fosite.Request{Session: &fosite.DefaultSession{}}) require.Nil(t, err) - c.AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") + AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") err = m.DeleteOpenIDConnectSession(ctx, "4321") require.Nil(t, err) @@ -159,7 +158,7 @@ func TestCreateGetDeleteRefreshTokenSession(t *testing.T) { res, err := m.GetRefreshTokenSession(ctx, "4321", &fosite.DefaultSession{}) require.Nil(t, err) - c.AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") + AssertObjectKeysEqual(t, &defaultRequest, res, "Scopes", "GrantedScopes", "Form", "Session") err = m.DeleteRefreshTokenSession(ctx, "4321") require.Nil(t, err) diff --git a/pkg/joinURL.go b/pkg/joinURL.go new file mode 100644 index 00000000000..1016fbc91b9 --- /dev/null +++ b/pkg/joinURL.go @@ -0,0 +1,26 @@ +package pkg + +import ( + "fmt" + "net/url" + "path" +) + +func JoinURLStrings(host string, parts ...string) string { + var trailing string + + last := parts[len(parts)-1] + if last[len(last)-1:] == "/" { + trailing = "/" + } + + u, err := url.Parse(host) + if err != nil { + return fmt.Sprintf("%s%s%s", path.Join(append([]string{u.Path}, parts...)...), trailing) + } + + if u.Path == "" { + u.Path = "/" + } + return fmt.Sprintf("%s://%s%s%s", u.Scheme, u.Host, path.Join(append([]string{u.Path}, parts...)...), trailing) +} diff --git a/pkg/joinURL_test.go b/pkg/joinURL_test.go new file mode 100644 index 00000000000..5e38e3514b6 --- /dev/null +++ b/pkg/joinURL_test.go @@ -0,0 +1,33 @@ +package pkg + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJoinURLStrings(t *testing.T) { + for k, c := range []struct { + give []string + get string + }{ + { + give: []string{"http://localhost/", "/home"}, + get: "http://localhost/home", + }, + { + give: []string{"http://localhost", "/home"}, + get: "http://localhost/home", + }, + { + give: []string{"https://localhost/", "/home"}, + get: "https://localhost/home", + }, + { + give: []string{"http://localhost/", "/home", "home/", "/home/"}, + get: "http://localhost/home/home/home/", + }, + } { + assert.Equal(t, c.get, JoinURLStrings(c.give[0], c.give[1:]...), "Case %d", k) + } +} diff --git a/pkg/secret.go b/pkg/secret.go index fa5e88932e5..32d145e3d65 100644 --- a/pkg/secret.go +++ b/pkg/secret.go @@ -1,6 +1,6 @@ package pkg -import "github.com/ory-am/common/rand/sequence" +import "github.com/ory/hydra/rand/sequence" var secretCharSet = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-.,!$%&/()=?><") diff --git a/pkg/url.go b/pkg/url.go index a4ff5d84e1a..87017a6b4b5 100644 --- a/pkg/url.go +++ b/pkg/url.go @@ -4,7 +4,6 @@ import ( "net/url" "path" - "github.com/ory-am/common/pkg" ) func CopyURL(u *url.URL) *url.URL { @@ -13,10 +12,6 @@ func CopyURL(u *url.URL) *url.URL { return a } -func JoinURLStrings(host string, args ...string) string { - return pkg.JoinURL(host, args...) -} - func JoinURL(u *url.URL, args ...string) (ep *url.URL) { ep = CopyURL(u) ep.Path = path.Join(append([]string{ep.Path}, args...)...) diff --git a/rand/README.md b/rand/README.md new file mode 100644 index 00000000000..d365aed6575 --- /dev/null +++ b/rand/README.md @@ -0,0 +1,42 @@ +# rand +A library based on crypto/rand to create random sequences, which are cryptographically strong. See: [crypto/rand](http://golang.org/pkg/crypto/rand/) + +## Install + +Run `go get github.com/ory-am/common/rand` + +## Usage + +### Create a random integer + +Create a random integer using [crypto/rand.Read](http://golang.org/pkg/crypto/rand/#Read): + +``` +import "github.com/ory-am/common/rand/numeric" +import "fmt" + +func main() { + fmt.Printf("%d", numeric.Int64()) + fmt.Printf("%d", numeric.UInt64()) + fmt.Printf("%d", numeric.Int32()) + fmt.Printf("%d", numeric.UInt32()) +} +``` + +### Create a random rune sequence / string + +Create a random string using [crypto/rand.Read](http://golang.org/pkg/crypto/rand/#Read): + +``` +import "github.com/ory-am/common/rand/sequence" +import "fmt" + +func main() { + allowed := []rune("abcdefghijklmnopqrstuvwxyz") + length := 10 + seq, err := sequence.RuneSequence(length, allowed) + + fmt.Printf("%s", seq) + fmt.Printf("%s", string(seq)) +} +``` \ No newline at end of file diff --git a/rand/doc.go b/rand/doc.go new file mode 100644 index 00000000000..c27c412e4d5 --- /dev/null +++ b/rand/doc.go @@ -0,0 +1,2 @@ +// A library based on crypto/rand to create random sequences +package rand diff --git a/rand/numeric/int.go b/rand/numeric/int.go new file mode 100644 index 00000000000..15b169c9456 --- /dev/null +++ b/rand/numeric/int.go @@ -0,0 +1,52 @@ +package numeric + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "io" +) + +var ( + rander = rand.Reader // random function + r = make([]byte, 8) +) + +// Int64 creates a random 64 bit integer using crypto.rand +func Int64() (i int64) { + randomBits(r) + buf := bytes.NewBuffer(r) + binary.Read(buf, binary.LittleEndian, &i) + return i +} + +// UInt64 creates a random 64 bit unsigned integer using crypto.rand +func UInt64() (i uint64) { + randomBits(r) + buf := bytes.NewBuffer(r) + binary.Read(buf, binary.LittleEndian, &i) + return i +} + +// Int32 creates a random 32 bit integer using crypto.rand +func Int32() (i int32) { + randomBits(r) + buf := bytes.NewBuffer(r) + binary.Read(buf, binary.LittleEndian, &i) + return i +} + +// UInt32 creates a random 32 bit unsigned integer using crypto.rand +func UInt32() (i uint32) { + randomBits(r) + buf := bytes.NewBuffer(r) + binary.Read(buf, binary.LittleEndian, &i) + return i +} + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} diff --git a/rand/numeric/int_test.go b/rand/numeric/int_test.go new file mode 100644 index 00000000000..b2667acee1a --- /dev/null +++ b/rand/numeric/int_test.go @@ -0,0 +1,97 @@ +package numeric + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInt64(t *testing.T) { + seq := Int64() + assert.NotEmpty(t, seq) +} + +func TestInt32(t *testing.T) { + seq := Int32() + assert.NotEmpty(t, seq) +} + +func TestUInt64(t *testing.T) { + seq := UInt64() + assert.NotEmpty(t, seq) +} + +func TestUInt32(t *testing.T) { + seq := UInt32() + assert.NotEmpty(t, seq) +} + +func TestInt64IsUnique(t *testing.T) { + // Probability of collision is around 1 in a million + times := 6000000 + s := make(map[int64]bool) + + for i := 0; i < times; i++ { + k := Int64() + _, ok := s[k] + assert.False(t, ok) + if ok { + return + } + s[k] = true + } +} + +func TestUInt64IsUnique(t *testing.T) { + // Probability of collision is around 1 in a million + times := 6000000 + s := make(map[uint64]bool) + + for i := 0; i < times; i++ { + k := UInt64() + _, ok := s[k] + assert.False(t, ok) + if ok { + return + } + s[k] = true + } +} + +func TestInt32IsUnique(t *testing.T) { + // Probability of collision is around 1 in 1000 + times := 3000 + s := make(map[int32]bool) + + for i := 0; i < times; i++ { + k := Int32() + _, ok := s[k] + assert.False(t, ok) + if ok { + return + } + s[k] = true + } +} + +func TestUInt32IsUnique(t *testing.T) { + // Probability of collision is around 1 in 1000 + times := 3000 + s := make(map[uint32]bool) + + for i := 0; i < times; i++ { + k := UInt32() + _, ok := s[k] + assert.False(t, ok) + if ok { + return + } + s[k] = true + } +} + +func BenchmarkTestInt64(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Int64() + } +} diff --git a/rand/sequence/sequence.go b/rand/sequence/sequence.go new file mode 100644 index 00000000000..dda13e907e9 --- /dev/null +++ b/rand/sequence/sequence.go @@ -0,0 +1,34 @@ +package sequence + +import ( + "crypto/rand" + "math/big" +) + +var rander = rand.Reader // random function + +var ( + AlphaNum = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + Alpha = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + AlphaLowerNum = []rune("abcdefghijklmnopqrstuvwxyz0123456789") + AlphaUpperNum = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + AlphaLower = []rune("abcdefghijklmnopqrstuvwxyz") + AlphaUpper = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + Numeric = []rune("0123456789") +) + +func RuneSequence(l int, allowedRunes []rune) (seq []rune, err error) { + c := big.NewInt(int64(len(allowedRunes))) + seq = make([]rune, l) + + for i := 0; i < l; i++ { + r, err := rand.Int(rander, c) + if err != nil { + return seq, err + } + rn := allowedRunes[r.Uint64()] + seq[i] = rn + } + + return seq, nil +} diff --git a/rand/sequence/sequence_test.go b/rand/sequence/sequence_test.go new file mode 100644 index 00000000000..d85d1e3fdaf --- /dev/null +++ b/rand/sequence/sequence_test.go @@ -0,0 +1,79 @@ +package sequence + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRunePatterns(t *testing.T) { + for k, v := range []struct { + runes []rune + shouldMatch string + }{ + {Alpha, "[a-zA-Z]{52}"}, + {AlphaLower, "[a-z]{26}"}, + {AlphaUpper, "[A-Z]{26}"}, + {AlphaNum, "[a-zA-Z0-9]{62}"}, + {AlphaLowerNum, "[a-z0-9]{36}"}, + {AlphaUpperNum, "[A-Z0-9]{36}"}, + {Numeric, "[0-9]{10}"}, + } { + valid, err := regexp.Match(v.shouldMatch, []byte(string(v.runes))) + assert.Nil(t, err, "Case %d", k) + assert.True(t, valid, "Case %d", k) + } +} + +func TestRuneSequenceMatchesPattern(t *testing.T) { + for k, v := range []struct { + runes []rune + shouldMatch string + length int + }{ + {Alpha, "[a-zA-Z]+", 25}, + {AlphaLower, "[a-z]+", 46}, + {AlphaUpper, "[A-Z]+", 21}, + {AlphaNum, "[a-zA-Z0-9]+", 123}, + {AlphaLowerNum, "[a-z0-9]+", 41}, + {AlphaUpperNum, "[A-Z0-9]+", 94914}, + {Numeric, "[0-9]+", 94914}, + } { + seq, err := RuneSequence(v.length, v.runes) + assert.Nil(t, err, "case %d", k) + assert.Equal(t, v.length, len(seq), "case %d", k) + + valid, err := regexp.Match(v.shouldMatch, []byte(string(seq))) + assert.Nil(t, err, "case %d", k) + assert.True(t, valid, "case %d\nrunes %s\nresult %s", k, v.runes, string(seq)) + } +} + +func TestRuneSequenceIsPseudoUnique(t *testing.T) { + // 1 in 100 probability of collision + times := 9000 + runes := []rune("ab") + length := 32 + s := make(map[string]bool) + + for i := 0; i < times; i++ { + k, err := RuneSequence(length, runes) + assert.Nil(t, err) + ks := string(k) + _, ok := s[ks] + assert.False(t, ok) + if ok { + return + } + s[ks] = true + } +} + +func BenchmarkTestInt64(b *testing.B) { + length := 25 + pattern := []rune("abcdefghijklmnopqrstuvwxyz") + for i := 0; i < b.N; i++ { + RuneSequence(length, pattern) + } +}