Feature: LDAP refactoring (#16950)

* incapsulates multipleldap logic under one module

* abstracts users upsert and get logic

* changes some of the text error messages and import sort sequence

* heavily refactors the LDAP module – LDAP module now only deals with LDAP related behaviour

* integrates affected auth_proxy module and their tests

* refactoring of the auth_proxy logic
pull/16820/head^2
Oleg Gaidarenko 6 years ago committed by GitHub
parent 1a80885180
commit 35f227de11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      go.mod
  2. 2
      go.sum
  3. 1
      pkg/extensions/main.go
  4. 15
      pkg/login/auth.go
  5. 32
      pkg/login/auth_test.go
  6. 38
      pkg/login/ldap_login.go
  7. 106
      pkg/login/ldap_login_test.go
  8. 6
      pkg/middleware/auth_proxy.go
  9. 136
      pkg/middleware/auth_proxy/auth_proxy.go
  10. 92
      pkg/middleware/auth_proxy/auth_proxy_test.go
  11. 3
      pkg/middleware/middleware.go
  12. 5
      pkg/services/ldap/hooks.go
  13. 582
      pkg/services/ldap/ldap.go
  14. 140
      pkg/services/ldap/ldap_helpers_test.go
  15. 120
      pkg/services/ldap/ldap_login_test.go
  16. 583
      pkg/services/ldap/ldap_test.go
  17. 6
      pkg/services/ldap/settings.go
  18. 49
      pkg/services/ldap/test.go
  19. 204
      pkg/services/multildap/multildap.go
  20. 10
      pkg/services/sqlstore/sqlstore.go
  21. 39
      pkg/services/user/user.go
  22. 1
      pkg/setting/setting.go
  23. 134
      vendor/github.com/brianvoe/gofakeit/BENCHMARKS.md
  24. 46
      vendor/github.com/brianvoe/gofakeit/CODE_OF_CONDUCT.md
  25. 1
      vendor/github.com/brianvoe/gofakeit/CONTRIBUTING.md
  26. 20
      vendor/github.com/brianvoe/gofakeit/LICENSE.txt
  27. 254
      vendor/github.com/brianvoe/gofakeit/README.md
  28. 3
      vendor/github.com/brianvoe/gofakeit/TODO.txt
  29. 131
      vendor/github.com/brianvoe/gofakeit/address.go
  30. 45
      vendor/github.com/brianvoe/gofakeit/beer.go
  31. 10
      vendor/github.com/brianvoe/gofakeit/bool.go
  32. 44
      vendor/github.com/brianvoe/gofakeit/color.go
  33. 30
      vendor/github.com/brianvoe/gofakeit/company.go
  34. 40
      vendor/github.com/brianvoe/gofakeit/contact.go
  35. 38
      vendor/github.com/brianvoe/gofakeit/currency.go
  36. 15
      vendor/github.com/brianvoe/gofakeit/data/address.go
  37. 10
      vendor/github.com/brianvoe/gofakeit/data/beer.go
  38. 7
      vendor/github.com/brianvoe/gofakeit/data/colors.go
  39. 9
      vendor/github.com/brianvoe/gofakeit/data/company.go
  40. 8
      vendor/github.com/brianvoe/gofakeit/data/computer.go
  41. 6
      vendor/github.com/brianvoe/gofakeit/data/contact.go
  42. 7
      vendor/github.com/brianvoe/gofakeit/data/currency.go
  43. 28
      vendor/github.com/brianvoe/gofakeit/data/data.go
  44. 9
      vendor/github.com/brianvoe/gofakeit/data/datetime.go
  45. 7
      vendor/github.com/brianvoe/gofakeit/data/files.go
  46. 20
      vendor/github.com/brianvoe/gofakeit/data/hacker.go
  47. 6
      vendor/github.com/brianvoe/gofakeit/data/hipster.go
  48. 8
      vendor/github.com/brianvoe/gofakeit/data/internet.go
  49. 8
      vendor/github.com/brianvoe/gofakeit/data/job.go
  50. 8
      vendor/github.com/brianvoe/gofakeit/data/log_level.go
  51. 6
      vendor/github.com/brianvoe/gofakeit/data/lorem.go
  52. 20
      vendor/github.com/brianvoe/gofakeit/data/payment.go
  53. 9
      vendor/github.com/brianvoe/gofakeit/data/person.go
  54. 7
      vendor/github.com/brianvoe/gofakeit/data/status_code.go
  55. 10
      vendor/github.com/brianvoe/gofakeit/data/vehicle.go
  56. 77
      vendor/github.com/brianvoe/gofakeit/datetime.go
  57. 10
      vendor/github.com/brianvoe/gofakeit/doc.go
  58. 15
      vendor/github.com/brianvoe/gofakeit/faker.go
  59. 11
      vendor/github.com/brianvoe/gofakeit/file.go
  60. 41
      vendor/github.com/brianvoe/gofakeit/generate.go
  61. 35
      vendor/github.com/brianvoe/gofakeit/hacker.go
  62. 20
      vendor/github.com/brianvoe/gofakeit/hipster.go
  63. 8
      vendor/github.com/brianvoe/gofakeit/image.go
  64. 55
      vendor/github.com/brianvoe/gofakeit/internet.go
  65. 34
      vendor/github.com/brianvoe/gofakeit/job.go
  66. 15
      vendor/github.com/brianvoe/gofakeit/log_level.go
  67. BIN
      vendor/github.com/brianvoe/gofakeit/logo.png
  68. 132
      vendor/github.com/brianvoe/gofakeit/misc.go
  69. 26
      vendor/github.com/brianvoe/gofakeit/name.go
  70. 84
      vendor/github.com/brianvoe/gofakeit/number.go
  71. 68
      vendor/github.com/brianvoe/gofakeit/password.go
  72. 81
      vendor/github.com/brianvoe/gofakeit/payment.go
  73. 45
      vendor/github.com/brianvoe/gofakeit/person.go
  74. 11
      vendor/github.com/brianvoe/gofakeit/status_code.go
  75. 48
      vendor/github.com/brianvoe/gofakeit/string.go
  76. 87
      vendor/github.com/brianvoe/gofakeit/struct.go
  77. 34
      vendor/github.com/brianvoe/gofakeit/unique.go
  78. 92
      vendor/github.com/brianvoe/gofakeit/user_agent.go
  79. 55
      vendor/github.com/brianvoe/gofakeit/vehicle.go
  80. 100
      vendor/github.com/brianvoe/gofakeit/words.go
  81. 3
      vendor/modules.txt

@ -9,6 +9,7 @@ require (
github.com/aws/aws-sdk-go v1.18.5
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
github.com/brianvoe/gofakeit v3.17.0+incompatible
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/codegangsta/cli v1.20.0
github.com/davecgh/go-spew v1.1.1

@ -16,6 +16,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/brianvoe/gofakeit v3.17.0+incompatible h1:C1+30+c0GtjgGDtRC+iePZeP1WMiwsWCELNJhmc7aIc=
github.com/brianvoe/gofakeit v3.17.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=

@ -1,6 +1,7 @@
package extensions
import (
_ "github.com/brianvoe/gofakeit"
_ "github.com/gobwas/glob"
_ "github.com/robfig/cron"
_ "gopkg.in/square/go-jose.v2"

@ -4,8 +4,8 @@ import (
"errors"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
LDAP "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
)
var (
@ -25,7 +25,8 @@ func Init() {
bus.AddHandler("auth", AuthenticateUser)
}
func AuthenticateUser(query *m.LoginUserQuery) error {
// AuthenticateUser authenticates the user via username & password
func AuthenticateUser(query *models.LoginUserQuery) error {
if err := validateLoginAttempts(query.Username); err != nil {
return err
}
@ -35,24 +36,24 @@ func AuthenticateUser(query *m.LoginUserQuery) error {
}
err := loginUsingGrafanaDB(query)
if err == nil || (err != m.ErrUserNotFound && err != ErrInvalidCredentials) {
if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials) {
return err
}
ldapEnabled, ldapErr := loginUsingLdap(query)
if ldapEnabled {
if ldapErr == nil || ldapErr != LDAP.ErrInvalidCredentials {
if ldapErr == nil || ldapErr != ldap.ErrInvalidCredentials {
return ldapErr
}
err = ldapErr
}
if err == ErrInvalidCredentials || err == LDAP.ErrInvalidCredentials {
if err == ErrInvalidCredentials || err == ldap.ErrInvalidCredentials {
saveInvalidLoginAttempt(query)
}
if err == m.ErrUserNotFound {
if err == models.ErrUserNotFound {
return ErrInvalidCredentials
}

@ -6,8 +6,8 @@ import (
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
LDAP "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
)
func TestAuthenticateUser(t *testing.T) {
@ -17,7 +17,7 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingGrafanaDB(nil, sc)
mockLoginUsingLdap(false, nil, sc)
loginQuery := m.LoginUserQuery{
loginQuery := models.LoginUserQuery{
Username: "user",
Password: "",
}
@ -84,7 +84,7 @@ func TestAuthenticateUser(t *testing.T) {
authScenario("When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) {
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc)
mockLoginUsingLdap(false, nil, sc)
mockSaveInvalidLoginAttempt(sc)
@ -101,14 +101,14 @@ func TestAuthenticateUser(t *testing.T) {
authScenario("When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) {
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
mockLoginUsingLdap(true, LDAP.ErrInvalidCredentials, sc)
mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc)
mockLoginUsingLdap(true, ldap.ErrInvalidCredentials, sc)
mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUser(sc.loginUserQuery)
Convey("it should result in", func() {
So(err, ShouldEqual, LDAP.ErrInvalidCredentials)
So(err, ShouldEqual, ldap.ErrInvalidCredentials)
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeTrue)
@ -118,7 +118,7 @@ func TestAuthenticateUser(t *testing.T) {
authScenario("When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) {
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc)
mockLoginUsingLdap(true, nil, sc)
mockSaveInvalidLoginAttempt(sc)
@ -136,7 +136,7 @@ func TestAuthenticateUser(t *testing.T) {
authScenario("When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) {
customErr := errors.New("custom")
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
mockLoginUsingGrafanaDB(models.ErrUserNotFound, sc)
mockLoginUsingLdap(true, customErr, sc)
mockSaveInvalidLoginAttempt(sc)
@ -154,13 +154,13 @@ func TestAuthenticateUser(t *testing.T) {
authScenario("When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) {
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc)
mockLoginUsingLdap(true, LDAP.ErrInvalidCredentials, sc)
mockLoginUsingLdap(true, ldap.ErrInvalidCredentials, sc)
mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUser(sc.loginUserQuery)
Convey("it should result in", func() {
So(err, ShouldEqual, LDAP.ErrInvalidCredentials)
So(err, ShouldEqual, ldap.ErrInvalidCredentials)
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeTrue)
@ -171,7 +171,7 @@ func TestAuthenticateUser(t *testing.T) {
}
type authScenarioContext struct {
loginUserQuery *m.LoginUserQuery
loginUserQuery *models.LoginUserQuery
grafanaLoginWasCalled bool
ldapLoginWasCalled bool
loginAttemptValidationWasCalled bool
@ -181,14 +181,14 @@ type authScenarioContext struct {
type authScenarioFunc func(sc *authScenarioContext)
func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) {
loginUsingGrafanaDB = func(query *m.LoginUserQuery) error {
loginUsingGrafanaDB = func(query *models.LoginUserQuery) error {
sc.grafanaLoginWasCalled = true
return err
}
}
func mockLoginUsingLdap(enabled bool, err error, sc *authScenarioContext) {
loginUsingLdap = func(query *m.LoginUserQuery) (bool, error) {
loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) {
sc.ldapLoginWasCalled = true
return enabled, err
}
@ -202,7 +202,7 @@ func mockLoginAttemptValidation(err error, sc *authScenarioContext) {
}
func mockSaveInvalidLoginAttempt(sc *authScenarioContext) {
saveInvalidLoginAttempt = func(query *m.LoginUserQuery) {
saveInvalidLoginAttempt = func(query *models.LoginUserQuery) {
sc.saveInvalidLoginAttemptWasCalled = true
}
}
@ -215,7 +215,7 @@ func authScenario(desc string, fn authScenarioFunc) {
origSaveInvalidLoginAttempt := saveInvalidLoginAttempt
sc := &authScenarioContext{
loginUserQuery: &m.LoginUserQuery{
loginUserQuery: &models.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",

@ -2,13 +2,20 @@ package login
import (
"github.com/grafana/grafana/pkg/models"
LDAP "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
)
var newLDAP = LDAP.New
var getLDAPConfig = LDAP.GetConfig
var isLDAPEnabled = LDAP.IsEnabled
// getLDAPConfig gets LDAP config
var getLDAPConfig = multildap.GetConfig
// isLDAPEnabled checks if LDAP is enabled
var isLDAPEnabled = multildap.IsEnabled
// newLDAP creates multiple LDAP instance
var newLDAP = multildap.New
// loginUsingLdap logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be
// populated with the logged in user if successful.
@ -23,18 +30,21 @@ var loginUsingLdap = func(query *models.LoginUserQuery) (bool, error) {
if err != nil {
return true, errutil.Wrap("Failed to get LDAP config", err)
}
if len(config.Servers) == 0 {
return true, ErrNoLDAPServers
}
for _, server := range config.Servers {
auth := newLDAP(server)
externalUser, err := newLDAP(config.Servers).Login(query)
if err != nil {
return true, err
}
err := auth.Login(query)
if err == nil || err != LDAP.ErrInvalidCredentials {
return true, err
}
login, err := user.Upsert(&user.UpsertArgs{
ExternalUser: externalUser,
SignupAllowed: setting.LdapAllowSignup,
})
if err != nil {
return true, err
}
return true, LDAP.ErrInvalidCredentials
query.User = login
return true, nil
}

@ -6,8 +6,9 @@ import (
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
LDAP "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting"
)
@ -18,11 +19,11 @@ func TestLdapLogin(t *testing.T) {
Convey("Given ldap enabled and no server configured", func() {
setting.LdapEnabled = true
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
LDAPLoginScenario("When login", func(sc *LDAPLoginScenarioContext) {
sc.withLoginResult(false)
getLDAPConfig = func() (*LDAP.Config, error) {
config := &LDAP.Config{
Servers: []*LDAP.ServerConfig{},
getLDAPConfig = func() (*ldap.Config, error) {
config := &ldap.Config{
Servers: []*ldap.ServerConfig{},
}
return config, nil
@ -35,11 +36,11 @@ func TestLdapLogin(t *testing.T) {
})
Convey("it should return no LDAP servers error", func() {
So(err, ShouldEqual, ErrNoLDAPServers)
So(err, ShouldEqual, errTest)
})
Convey("it should not call ldap login", func() {
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse)
So(sc.LDAPAuthenticatorMock.loginCalled, ShouldBeTrue)
})
})
})
@ -47,9 +48,9 @@ func TestLdapLogin(t *testing.T) {
Convey("Given ldap disabled", func() {
setting.LdapEnabled = false
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
LDAPLoginScenario("When login", func(sc *LDAPLoginScenarioContext) {
sc.withLoginResult(false)
enabled, err := loginUsingLdap(&m.LoginUserQuery{
enabled, err := loginUsingLdap(&models.LoginUserQuery{
Username: "user",
Password: "pwd",
})
@ -63,75 +64,88 @@ func TestLdapLogin(t *testing.T) {
})
Convey("it should not call ldap login", func() {
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse)
So(sc.LDAPAuthenticatorMock.loginCalled, ShouldBeFalse)
})
})
})
})
}
func mockLdapAuthenticator(valid bool) *mockAuth {
mock := &mockAuth{
validLogin: valid,
}
newLDAP = func(server *LDAP.ServerConfig) LDAP.IAuth {
return mock
}
return mock
}
type mockAuth struct {
validLogin bool
loginCalled bool
}
func (auth *mockAuth) Login(query *m.LoginUserQuery) error {
func (auth *mockAuth) Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo,
error,
) {
auth.loginCalled = true
if !auth.validLogin {
return errTest
return nil, errTest
}
return nil
return nil, nil
}
func (auth *mockAuth) Users() ([]*LDAP.UserInfo, error) {
func (auth *mockAuth) Users(logins []string) (
[]*models.ExternalUserInfo,
error,
) {
return nil, nil
}
func (auth *mockAuth) SyncUser(query *m.LoginUserQuery) error {
func (auth *mockAuth) User(login string) (
*models.ExternalUserInfo,
error,
) {
return nil, nil
}
func (auth *mockAuth) Add(dn string, values map[string][]string) error {
return nil
}
func (auth *mockAuth) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LDAP.UserInfo) (*m.User, error) {
return nil, nil
func (auth *mockAuth) Remove(dn string) error {
return nil
}
func mockLDAPAuthenticator(valid bool) *mockAuth {
mock := &mockAuth{
validLogin: valid,
}
newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP {
return mock
}
return mock
}
type ldapLoginScenarioContext struct {
loginUserQuery *m.LoginUserQuery
ldapAuthenticatorMock *mockAuth
type LDAPLoginScenarioContext struct {
loginUserQuery *models.LoginUserQuery
LDAPAuthenticatorMock *mockAuth
}
type ldapLoginScenarioFunc func(c *ldapLoginScenarioContext)
type LDAPLoginScenarioFunc func(c *LDAPLoginScenarioContext)
func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) {
func LDAPLoginScenario(desc string, fn LDAPLoginScenarioFunc) {
Convey(desc, func() {
mock := &mockAuth{}
sc := &ldapLoginScenarioContext{
loginUserQuery: &m.LoginUserQuery{
sc := &LDAPLoginScenarioContext{
loginUserQuery: &models.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
},
ldapAuthenticatorMock: mock,
LDAPAuthenticatorMock: mock,
}
getLDAPConfig = func() (*LDAP.Config, error) {
config := &LDAP.Config{
Servers: []*LDAP.ServerConfig{
getLDAPConfig = func() (*ldap.Config, error) {
config := &ldap.Config{
Servers: []*ldap.ServerConfig{
{
Host: "",
},
@ -141,19 +155,19 @@ func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) {
return config, nil
}
newLDAP = func(server *LDAP.ServerConfig) LDAP.IAuth {
newLDAP = func(server []*ldap.ServerConfig) multildap.IMultiLDAP {
return mock
}
defer func() {
newLDAP = LDAP.New
getLDAPConfig = LDAP.GetConfig
newLDAP = multildap.New
getLDAPConfig = multildap.GetConfig
}()
fn(sc)
})
}
func (sc *ldapLoginScenarioContext) withLoginResult(valid bool) {
sc.ldapAuthenticatorMock = mockLdapAuthenticator(valid)
func (sc *LDAPLoginScenarioContext) withLoginResult(valid bool) {
sc.LDAPAuthenticatorMock = mockLDAPAuthenticator(valid)
}

@ -35,8 +35,8 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext,
return true
}
// Try to get user id from various sources
id, err := auth.GetUserID()
// Try to log in user from various providers
id, err := auth.Login()
if err != nil {
ctx.Handle(500, err.Error(), err.DetailsError)
return true
@ -54,7 +54,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext,
ctx.IsSignedIn = true
// Remember user data it in cache
if err := auth.Remember(); err != nil {
if err := auth.Remember(id); err != nil {
ctx.Handle(500, err.Error(), err.DetailsError)
return true
}

@ -12,6 +12,8 @@ import (
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
@ -21,10 +23,14 @@ const (
CachePrefix = "auth-proxy-sync-ttl:%s"
)
var (
getLDAPConfig = ldap.GetConfig
isLDAPEnabled = ldap.IsEnabled
)
// getLDAPConfig gets LDAP config
var getLDAPConfig = ldap.GetConfig
// isLDAPEnabled checks if LDAP is enabled
var isLDAPEnabled = ldap.IsEnabled
// newLDAP creates multiple LDAP instance
var newLDAP = multildap.New
// AuthProxy struct
type AuthProxy struct {
@ -33,13 +39,13 @@ type AuthProxy struct {
orgID int64
header string
LDAP func(server *ldap.ServerConfig) ldap.IAuth
enabled bool
whitelistIP string
headerType string
headers map[string]string
cacheTTL int
enabled bool
LdapAllowSignup bool
AuthProxyAutoSignUp bool
whitelistIP string
headerType string
headers map[string]string
cacheTTL int
}
// Error auth proxy specific error
@ -78,13 +84,13 @@ func New(options *Options) *AuthProxy {
orgID: options.OrgID,
header: header,
LDAP: ldap.New,
enabled: setting.AuthProxyEnabled,
headerType: setting.AuthProxyHeaderProperty,
headers: setting.AuthProxyHeaders,
whitelistIP: setting.AuthProxyWhitelist,
cacheTTL: setting.AuthProxyLdapSyncTtl,
enabled: setting.AuthProxyEnabled,
headerType: setting.AuthProxyHeaderProperty,
headers: setting.AuthProxyHeaders,
whitelistIP: setting.AuthProxyWhitelist,
cacheTTL: setting.AuthProxyLdapSyncTtl,
LdapAllowSignup: setting.LdapAllowSignup,
AuthProxyAutoSignUp: setting.AuthProxyAutoSignUp,
}
}
@ -144,34 +150,22 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) {
return false, newError("Proxy authentication required", err)
}
// InCache checks if we have user in cache
func (auth *AuthProxy) InCache() bool {
userID, _ := auth.GetUserIDViaCache()
if userID == 0 {
return false
}
return true
}
// getKey forms a key for the cache
func (auth *AuthProxy) getKey() string {
return fmt.Sprintf(CachePrefix, auth.header)
}
// GetUserID gets user id with whatever means possible
func (auth *AuthProxy) GetUserID() (int64, *Error) {
if auth.InCache() {
// Login logs in user id with whatever means possible
func (auth *AuthProxy) Login() (int64, *Error) {
id, _ := auth.GetUserViaCache()
if id != 0 {
// Error here means absent cache - we don't need to handle that
id, _ := auth.GetUserIDViaCache()
return id, nil
}
if isLDAPEnabled() {
id, err := auth.GetUserIDViaLDAP()
id, err := auth.LoginViaLDAP()
if err == ldap.ErrInvalidCredentials {
return 0, newError(
@ -181,16 +175,16 @@ func (auth *AuthProxy) GetUserID() (int64, *Error) {
}
if err != nil {
return 0, newError("Failed to sync user", err)
return 0, newError("Failed to get the user", err)
}
return id, nil
}
id, err := auth.GetUserIDViaHeader()
id, err := auth.LoginViaHeader()
if err != nil {
return 0, newError(
"Failed to login as user specified in auth proxy header",
"Failed to log in as user, specified in auth proxy header",
err,
)
}
@ -198,8 +192,8 @@ func (auth *AuthProxy) GetUserID() (int64, *Error) {
return id, nil
}
// GetUserIDViaCache gets the user from cache
func (auth *AuthProxy) GetUserIDViaCache() (int64, error) {
// GetUserViaCache gets user id from cache
func (auth *AuthProxy) GetUserViaCache() (int64, error) {
var (
cacheKey = auth.getKey()
userID, err = auth.store.Get(cacheKey)
@ -212,33 +206,34 @@ func (auth *AuthProxy) GetUserIDViaCache() (int64, error) {
return userID.(int64), nil
}
// GetUserIDViaLDAP gets user via LDAP request
func (auth *AuthProxy) GetUserIDViaLDAP() (int64, *Error) {
query := &models.LoginUserQuery{
ReqContext: auth.ctx,
Username: auth.header,
}
// LoginViaLDAP logs in user via LDAP request
func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) {
config, err := getLDAPConfig()
if err != nil {
return 0, newError("Failed to get LDAP config", nil)
}
if len(config.Servers) == 0 {
return 0, newError("No LDAP servers available", nil)
extUser, err := newLDAP(config.Servers).User(auth.header)
if err != nil {
return 0, newError(err.Error(), nil)
}
for _, server := range config.Servers {
author := auth.LDAP(server)
if err := author.SyncUser(query); err != nil {
return 0, newError(err.Error(), nil)
}
// Have to sync grafana and LDAP user during log in
user, err := user.Upsert(&user.UpsertArgs{
ReqContext: auth.ctx,
SignupAllowed: auth.LdapAllowSignup,
ExternalUser: extUser,
})
if err != nil {
return 0, newError(err.Error(), nil)
}
return query.User.Id, nil
return user.Id, nil
}
// GetUserIDViaHeader gets user from the header only
func (auth *AuthProxy) GetUserIDViaHeader() (int64, error) {
// LoginViaHeader logs in user from the header only
// TODO: refactor - cyclomatic complexity should be much lower
func (auth *AuthProxy) LoginViaHeader() (int64, error) {
extUser := &models.ExternalUserInfo{
AuthModule: "authproxy",
AuthId: auth.header,
@ -269,18 +264,16 @@ func (auth *AuthProxy) GetUserIDViaHeader() (int64, error) {
}
}
// add/update user in grafana
cmd := &models.UpsertUserCommand{
result, err := user.Upsert(&user.UpsertArgs{
ReqContext: auth.ctx,
SignupAllowed: true,
ExternalUser: extUser,
SignupAllowed: setting.AuthProxyAutoSignUp,
}
err := bus.Dispatch(cmd)
})
if err != nil {
return 0, err
}
return cmd.Result.Id, nil
return result.Id, nil
}
// GetSignedUser get full signed user info
@ -298,21 +291,18 @@ func (auth *AuthProxy) GetSignedUser(userID int64) (*models.SignedInUser, *Error
}
// Remember user in cache
func (auth *AuthProxy) Remember() *Error {
func (auth *AuthProxy) Remember(id int64) *Error {
key := auth.getKey()
// Make sure we do not rewrite the expiration time
if auth.InCache() {
// Check if user already in cache
userID, _ := auth.store.Get(key)
if userID != nil {
return nil
}
var (
key = auth.getKey()
value, _ = auth.GetUserIDViaCache()
expiration = time.Duration(-auth.cacheTTL) * time.Minute
err = auth.store.Set(key, value, expiration)
)
expiration := time.Duration(-auth.cacheTTL) * time.Minute
err := auth.store.Set(key, id, expiration)
if err != nil {
return newError(err.Error(), nil)
}

@ -1,6 +1,7 @@
package authproxy
import (
"errors"
"fmt"
"net/http"
"testing"
@ -8,24 +9,40 @@ import (
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting"
)
type TestLDAP struct {
ldap.Auth
ID int64
syncCalled bool
type TestMultiLDAP struct {
multildap.MultiLDAP
ID int64
userCalled bool
loginCalled bool
}
func (stub *TestLDAP) SyncUser(query *models.LoginUserQuery) error {
stub.syncCalled = true
query.User = &models.User{
Id: stub.ID,
func (stub *TestMultiLDAP) Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo, error,
) {
stub.loginCalled = true
result := &models.ExternalUserInfo{
UserId: stub.ID,
}
return nil
return result, nil
}
func (stub *TestMultiLDAP) User(login string) (
*models.ExternalUserInfo,
error,
) {
stub.userCalled = true
result := &models.ExternalUserInfo{
UserId: stub.ID,
}
return result, nil
}
func TestMiddlewareContext(t *testing.T) {
@ -44,7 +61,7 @@ func TestMiddlewareContext(t *testing.T) {
},
}
Convey("gets data from the cache", func() {
Convey("logs in user from the cache", func() {
store := remotecache.NewFakeStore(t)
key := fmt.Sprintf(CachePrefix, name)
store.Set(key, int64(33), 0)
@ -55,53 +72,64 @@ func TestMiddlewareContext(t *testing.T) {
OrgID: 4,
})
id, err := auth.GetUserID()
id, err := auth.Login()
So(err, ShouldBeNil)
So(id, ShouldEqual, 33)
})
Convey("LDAP", func() {
Convey("gets data from the LDAP", func() {
Convey("logs in via LDAP", func() {
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
cmd.Result = &models.User{
Id: 42,
}
return nil
})
isLDAPEnabled = func() bool {
return true
}
stub := &TestMultiLDAP{
ID: 42,
}
getLDAPConfig = func() (*ldap.Config, error) {
config := &ldap.Config{
Servers: []*ldap.ServerConfig{
{},
{
SearchBaseDNs: []string{"BaseDNHere"},
},
},
}
return config, nil
}
newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP {
return stub
}
defer func() {
newLDAP = multildap.New
isLDAPEnabled = ldap.IsEnabled
getLDAPConfig = ldap.GetConfig
}()
store := remotecache.NewFakeStore(t)
auth := New(&Options{
server := New(&Options{
Store: store,
Ctx: ctx,
OrgID: 4,
})
stub := &TestLDAP{
ID: 42,
}
auth.LDAP = func(server *ldap.ServerConfig) ldap.IAuth {
return stub
}
id, err := auth.GetUserID()
id, err := server.Login()
So(err, ShouldBeNil)
So(id, ShouldEqual, 42)
So(stub.syncCalled, ShouldEqual, true)
So(stub.userCalled, ShouldEqual, true)
})
Convey("gets nice error if ldap is enabled but not configured", func() {
@ -110,13 +138,11 @@ func TestMiddlewareContext(t *testing.T) {
}
getLDAPConfig = func() (*ldap.Config, error) {
config := &ldap.Config{
Servers: []*ldap.ServerConfig{},
}
return config, nil
return nil, errors.New("Something went wrong")
}
defer func() {
newLDAP = multildap.New
isLDAPEnabled = ldap.IsEnabled
getLDAPConfig = ldap.GetConfig
}()
@ -129,20 +155,20 @@ func TestMiddlewareContext(t *testing.T) {
OrgID: 4,
})
stub := &TestLDAP{
stub := &TestMultiLDAP{
ID: 42,
}
auth.LDAP = func(server *ldap.ServerConfig) ldap.IAuth {
newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP {
return stub
}
id, err := auth.GetUserID()
id, err := auth.Login()
So(err, ShouldNotBeNil)
So(err.Error(), ShouldContainSubstring, "Failed to sync user")
So(err.Error(), ShouldContainSubstring, "Failed to get the user")
So(id, ShouldNotEqual, 42)
So(stub.syncCalled, ShouldEqual, false)
So(stub.loginCalled, ShouldEqual, false)
})
})

@ -7,6 +7,8 @@ import (
"strings"
"time"
macaron "gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/infra/log"
@ -14,7 +16,6 @@ import (
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
macaron "gopkg.in/macaron.v1"
)
var (

@ -1,5 +0,0 @@
package ldap
var (
hookDial func(*Auth) error
)

@ -8,39 +8,38 @@ import (
"io/ioutil"
"strings"
"github.com/davecgh/go-spew/spew"
LDAP "gopkg.in/ldap.v3"
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
models "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/models"
)
// IConnection is interface for LDAP connection manipulation
type IConnection interface {
Bind(username, password string) error
UnauthenticatedBind(username string) error
Search(*LDAP.SearchRequest) (*LDAP.SearchResult, error)
Add(*ldap.AddRequest) error
Del(*ldap.DelRequest) error
Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
StartTLS(*tls.Config) error
Close()
}
// IAuth is interface for LDAP authorization
type IAuth interface {
Login(query *models.LoginUserQuery) error
SyncUser(query *models.LoginUserQuery) error
GetGrafanaUserFor(
ctx *models.ReqContext,
user *UserInfo,
) (*models.User, error)
Users() ([]*UserInfo, error)
// IServer is interface for LDAP authorization
type IServer interface {
Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
Add(string, map[string][]string) error
Remove(string) error
Users([]string) ([]*models.ExternalUserInfo, error)
ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error)
Dial() error
Close()
}
// Auth is basic struct of LDAP authorization
type Auth struct {
server *ServerConfig
conn IConnection
// Server is basic struct of LDAP authorization
type Server struct {
config *ServerConfig
connection IConnection
requireSecondBind bool
log log.Logger
}
@ -52,28 +51,24 @@ var (
)
var dial = func(network, addr string) (IConnection, error) {
return LDAP.Dial(network, addr)
return ldap.Dial(network, addr)
}
// New creates the new LDAP auth
func New(server *ServerConfig) IAuth {
return &Auth{
server: server,
func New(config *ServerConfig) IServer {
return &Server{
config: config,
log: log.New("ldap"),
}
}
// Dial dials in the LDAP
func (auth *Auth) Dial() error {
if hookDial != nil {
return hookDial(auth)
}
func (server *Server) Dial() error {
var err error
var certPool *x509.CertPool
if auth.server.RootCACert != "" {
if server.config.RootCACert != "" {
certPool = x509.NewCertPool()
for _, caCertFile := range strings.Split(auth.server.RootCACert, " ") {
for _, caCertFile := range strings.Split(server.config.RootCACert, " ") {
pem, err := ioutil.ReadFile(caCertFile)
if err != nil {
return err
@ -84,35 +79,35 @@ func (auth *Auth) Dial() error {
}
}
var clientCert tls.Certificate
if auth.server.ClientCert != "" && auth.server.ClientKey != "" {
clientCert, err = tls.LoadX509KeyPair(auth.server.ClientCert, auth.server.ClientKey)
if server.config.ClientCert != "" && server.config.ClientKey != "" {
clientCert, err = tls.LoadX509KeyPair(server.config.ClientCert, server.config.ClientKey)
if err != nil {
return err
}
}
for _, host := range strings.Split(auth.server.Host, " ") {
address := fmt.Sprintf("%s:%d", host, auth.server.Port)
if auth.server.UseSSL {
for _, host := range strings.Split(server.config.Host, " ") {
address := fmt.Sprintf("%s:%d", host, server.config.Port)
if server.config.UseSSL {
tlsCfg := &tls.Config{
InsecureSkipVerify: auth.server.SkipVerifySSL,
InsecureSkipVerify: server.config.SkipVerifySSL,
ServerName: host,
RootCAs: certPool,
}
if len(clientCert.Certificate) > 0 {
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
}
if auth.server.StartTLS {
auth.conn, err = dial("tcp", address)
if server.config.StartTLS {
server.connection, err = dial("tcp", address)
if err == nil {
if err = auth.conn.StartTLS(tlsCfg); err == nil {
if err = server.connection.StartTLS(tlsCfg); err == nil {
return nil
}
}
} else {
auth.conn, err = LDAP.DialTLS("tcp", address, tlsCfg)
server.connection, err = ldap.DialTLS("tcp", address, tlsCfg)
}
} else {
auth.conn, err = dial("tcp", address)
server.connection, err = dial("tcp", address)
}
if err == nil {
@ -122,91 +117,206 @@ func (auth *Auth) Dial() error {
return err
}
// Login logs in the user
func (auth *Auth) Login(query *models.LoginUserQuery) error {
// connect to ldap server
if err := auth.Dial(); err != nil {
return err
}
defer auth.conn.Close()
// Close closes the LDAP connection
func (server *Server) Close() {
server.connection.Close()
}
// perform initial authentication
if err := auth.initialBind(query.Username, query.Password); err != nil {
return err
// Login intialBinds the user, search it and then serialize it
func (server *Server) Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo, error,
) {
// Perform initial authentication
err := server.intialBind(query.Username, query.Password)
if err != nil {
return nil, err
}
// find user entry & attributes
user, err := auth.searchForUser(query.Username)
// Find user entry & attributes
users, err := server.Users([]string{query.Username})
if err != nil {
return err
return nil, err
}
auth.log.Debug("Ldap User found", "info", spew.Sdump(user))
// If we couldn't find the user -
// we should show incorrect credentials err
if len(users) == 0 {
return nil, ErrInvalidCredentials
}
// check if a second user bind is needed
if auth.requireSecondBind {
err = auth.secondBind(user, query.Password)
// Check if a second user bind is needed
user := users[0]
if server.requireSecondBind {
err = server.secondBind(user, query.Password)
if err != nil {
return err
return nil, err
}
}
grafanaUser, err := auth.GetGrafanaUserFor(query.ReqContext, user)
return user, nil
}
// Add adds stuff to LDAP
func (server *Server) Add(dn string, values map[string][]string) error {
err := server.intialBind(
server.config.BindDN,
server.config.BindPassword,
)
if err != nil {
return err
}
query.User = grafanaUser
return nil
}
attributes := make([]ldap.Attribute, 0)
for key, value := range values {
attributes = append(attributes, ldap.Attribute{
Type: key,
Vals: value,
})
}
// SyncUser syncs user with Grafana
func (auth *Auth) SyncUser(query *models.LoginUserQuery) error {
// connect to ldap server
err := auth.Dial()
request := &ldap.AddRequest{
DN: dn,
Attributes: attributes,
}
err = server.connection.Add(request)
if err != nil {
return err
}
defer auth.conn.Close()
err = auth.serverBind()
return nil
}
// Remove removes stuff from LDAP
func (server *Server) Remove(dn string) error {
err := server.intialBind(
server.config.BindDN,
server.config.BindPassword,
)
if err != nil {
return err
}
// find user entry & attributes
user, err := auth.searchForUser(query.Username)
request := ldap.NewDelRequest(dn, nil)
err = server.connection.Del(request)
if err != nil {
auth.log.Error("Failed searching for user in ldap", "error", err)
return err
}
auth.log.Debug("Ldap User found", "info", spew.Sdump(user))
return nil
}
// Users gets LDAP users
func (server *Server) Users(logins []string) (
[]*models.ExternalUserInfo,
error,
) {
var result *ldap.SearchResult
var err error
var config = server.config
grafanaUser, err := auth.GetGrafanaUserFor(query.ReqContext, user)
for _, base := range config.SearchBaseDNs {
result, err = server.connection.Search(
server.getSearchRequest(base, logins),
)
if err != nil {
return nil, err
}
if len(result.Entries) > 0 {
break
}
}
serializedUsers, err := server.serializeUsers(result)
if err != nil {
return err
return nil, err
}
return serializedUsers, nil
}
// ExtractGrafanaUser extracts external user info from LDAP user
func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserInfo, error) {
result := server.buildGrafanaUser(user)
if err := server.validateGrafanaUser(result); err != nil {
return nil, err
}
return result, nil
}
// validateGrafanaUser validates user access.
// If there are no ldap group mappings access is true
// otherwise a single group must match
func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 {
server.log.Error(
"user does not belong in any of the specified LDAP groups",
"username", user.Login,
"groups", user.Groups,
)
return ErrInvalidCredentials
}
query.User = grafanaUser
return nil
}
func (auth *Auth) GetGrafanaUserFor(
ctx *models.ReqContext,
user *UserInfo,
) (*models.User, error) {
// getSearchRequest returns LDAP search request for users
func (server *Server) getSearchRequest(
base string,
logins []string,
) *ldap.SearchRequest {
attributes := []string{}
inputs := server.config.Attr
attributes = appendIfNotEmpty(
attributes,
inputs.Username,
inputs.Surname,
inputs.Email,
inputs.Name,
inputs.MemberOf,
)
search := ""
for _, login := range logins {
query := strings.Replace(
server.config.SearchFilter,
"%s", ldap.EscapeFilter(login),
-1,
)
search = search + query
}
filter := fmt.Sprintf("(|%s)", search)
return &ldap.SearchRequest{
BaseDN: base,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
Attributes: attributes,
Filter: filter,
}
}
// buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo {
extUser := &models.ExternalUserInfo{
AuthModule: "ldap",
AuthId: user.DN,
Name: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
Login: user.Username,
Email: user.Email,
Groups: user.MemberOf,
OrgRoles: map[int64]models.RoleType{},
Name: strings.TrimSpace(
fmt.Sprintf("%s %s", user.FirstName, user.LastName),
),
Login: user.Username,
Email: user.Email,
Groups: user.MemberOf,
OrgRoles: map[int64]models.RoleType{},
}
for _, group := range auth.server.Groups {
for _, group := range server.config.Groups {
// only use the first match for each org
if extUser.OrgRoles[group.OrgId] != "" {
continue
@ -220,49 +330,28 @@ func (auth *Auth) GetGrafanaUserFor(
}
}
// validate that the user has access
// if there are no ldap group mappings access is true
// otherwise a single group must match
if len(auth.server.Groups) > 0 && len(extUser.OrgRoles) < 1 {
auth.log.Info(
"Ldap Auth: user does not belong in any of the specified ldap groups",
"username", user.Username,
"groups", user.MemberOf,
)
return nil, ErrInvalidCredentials
}
// add/update user in grafana
upsertUserCmd := &models.UpsertUserCommand{
ReqContext: ctx,
ExternalUser: extUser,
SignupAllowed: setting.LdapAllowSignup,
}
err := bus.Dispatch(upsertUserCmd)
if err != nil {
return nil, err
}
return upsertUserCmd.Result, nil
return extUser
}
func (auth *Auth) serverBind() error {
func (server *Server) serverBind() error {
bindFn := func() error {
return auth.conn.Bind(auth.server.BindDN, auth.server.BindPassword)
return server.connection.Bind(
server.config.BindDN,
server.config.BindPassword,
)
}
if auth.server.BindPassword == "" {
if server.config.BindPassword == "" {
bindFn = func() error {
return auth.conn.UnauthenticatedBind(auth.server.BindDN)
return server.connection.UnauthenticatedBind(server.config.BindDN)
}
}
// bind_dn and bind_password to bind
if err := bindFn(); err != nil {
auth.log.Info("LDAP initial bind failed, %v", err)
server.log.Info("LDAP initial bind failed, %v", err)
if ldapErr, ok := err.(*LDAP.Error); ok {
if ldapErr, ok := err.(*ldap.Error); ok {
if ldapErr.ResultCode == 49 {
return ErrInvalidCredentials
}
@ -273,11 +362,15 @@ func (auth *Auth) serverBind() error {
return nil
}
func (auth *Auth) secondBind(user *UserInfo, userPassword string) error {
if err := auth.conn.Bind(user.DN, userPassword); err != nil {
auth.log.Info("Second bind failed", "error", err)
func (server *Server) secondBind(
user *models.ExternalUserInfo,
userPassword string,
) error {
err := server.connection.Bind(user.AuthId, userPassword)
if err != nil {
server.log.Info("Second bind failed", "error", err)
if ldapErr, ok := err.(*LDAP.Error); ok {
if ldapErr, ok := err.(*ldap.Error); ok {
if ldapErr.ResultCode == 49 {
return ErrInvalidCredentials
}
@ -288,31 +381,31 @@ func (auth *Auth) secondBind(user *UserInfo, userPassword string) error {
return nil
}
func (auth *Auth) initialBind(username, userPassword string) error {
if auth.server.BindPassword != "" || auth.server.BindDN == "" {
userPassword = auth.server.BindPassword
auth.requireSecondBind = true
func (server *Server) intialBind(username, userPassword string) error {
if server.config.BindPassword != "" || server.config.BindDN == "" {
userPassword = server.config.BindPassword
server.requireSecondBind = true
}
bindPath := auth.server.BindDN
bindPath := server.config.BindDN
if strings.Contains(bindPath, "%s") {
bindPath = fmt.Sprintf(auth.server.BindDN, username)
bindPath = fmt.Sprintf(server.config.BindDN, username)
}
bindFn := func() error {
return auth.conn.Bind(bindPath, userPassword)
return server.connection.Bind(bindPath, userPassword)
}
if userPassword == "" {
bindFn = func() error {
return auth.conn.UnauthenticatedBind(bindPath)
return server.connection.UnauthenticatedBind(bindPath)
}
}
if err := bindFn(); err != nil {
auth.log.Info("Initial bind failed", "error", err)
server.log.Info("Initial bind failed", "error", err)
if ldapErr, ok := err.(*LDAP.Error); ok {
if ldapErr, ok := err.(*ldap.Error); ok {
if ldapErr.ResultCode == 49 {
return ErrInvalidCredentials
}
@ -323,199 +416,124 @@ func (auth *Auth) initialBind(username, userPassword string) error {
return nil
}
func (auth *Auth) searchForUser(username string) (*UserInfo, error) {
var searchResult *LDAP.SearchResult
var err error
for _, searchBase := range auth.server.SearchBaseDNs {
attributes := make([]string, 0)
inputs := auth.server.Attr
attributes = appendIfNotEmpty(attributes,
inputs.Username,
inputs.Surname,
inputs.Email,
inputs.Name,
inputs.MemberOf)
searchReq := LDAP.SearchRequest{
BaseDN: searchBase,
Scope: LDAP.ScopeWholeSubtree,
DerefAliases: LDAP.NeverDerefAliases,
Attributes: attributes,
Filter: strings.Replace(
auth.server.SearchFilter,
"%s", LDAP.EscapeFilter(username),
-1,
),
}
auth.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq))
searchResult, err = auth.conn.Search(&searchReq)
if err != nil {
return nil, err
}
if len(searchResult.Entries) > 0 {
break
}
}
if len(searchResult.Entries) == 0 {
return nil, ErrInvalidCredentials
}
if len(searchResult.Entries) > 1 {
return nil, errors.New("Ldap search matched more than one entry, please review your filter setting")
}
// requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
var memberOf []string
if auth.server.GroupSearchFilter == "" {
memberOf = getLdapAttrArray(auth.server.Attr.MemberOf, searchResult)
} else {
// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
var groupSearchResult *LDAP.SearchResult
for _, groupSearchBase := range auth.server.GroupSearchBaseDNs {
var filter_replace string
if auth.server.GroupSearchFilterUserAttribute == "" {
filter_replace = getLdapAttr(auth.server.Attr.Username, searchResult)
} else {
filter_replace = getLdapAttr(auth.server.GroupSearchFilterUserAttribute, searchResult)
}
filter := strings.Replace(
auth.server.GroupSearchFilter, "%s",
LDAP.EscapeFilter(filter_replace),
-1,
)
auth.log.Info("Searching for user's groups", "filter", filter)
// support old way of reading settings
groupIdAttribute := auth.server.Attr.MemberOf
// but prefer dn attribute if default settings are used
if groupIdAttribute == "" || groupIdAttribute == "memberOf" {
groupIdAttribute = "dn"
}
groupSearchReq := LDAP.SearchRequest{
BaseDN: groupSearchBase,
Scope: LDAP.ScopeWholeSubtree,
DerefAliases: LDAP.NeverDerefAliases,
Attributes: []string{groupIdAttribute},
Filter: filter,
}
groupSearchResult, err = auth.conn.Search(&groupSearchReq)
if err != nil {
return nil, err
}
if len(groupSearchResult.Entries) > 0 {
for i := range groupSearchResult.Entries {
memberOf = append(memberOf, getLdapAttrN(groupIdAttribute, groupSearchResult, i))
}
break
}
for _, groupSearchBase := range server.config.GroupSearchBaseDNs {
var filterReplace string
if server.config.GroupSearchFilterUserAttribute == "" {
filterReplace = getLdapAttr(server.config.Attr.Username, searchResult)
} else {
filterReplace = getLdapAttr(server.config.GroupSearchFilterUserAttribute, searchResult)
}
}
return &UserInfo{
DN: searchResult.Entries[0].DN,
LastName: getLdapAttr(auth.server.Attr.Surname, searchResult),
FirstName: getLdapAttr(auth.server.Attr.Name, searchResult),
Username: getLdapAttr(auth.server.Attr.Username, searchResult),
Email: getLdapAttr(auth.server.Attr.Email, searchResult),
MemberOf: memberOf,
}, nil
}
func (ldap *Auth) Users() ([]*UserInfo, error) {
var result *LDAP.SearchResult
var err error
server := ldap.server
if err := ldap.Dial(); err != nil {
return nil, err
}
defer ldap.conn.Close()
for _, base := range server.SearchBaseDNs {
attributes := make([]string, 0)
inputs := server.Attr
attributes = appendIfNotEmpty(
attributes,
inputs.Username,
inputs.Surname,
inputs.Email,
inputs.Name,
inputs.MemberOf,
filter := strings.Replace(
server.config.GroupSearchFilter, "%s",
ldap.EscapeFilter(filterReplace),
-1,
)
req := LDAP.SearchRequest{
BaseDN: base,
Scope: LDAP.ScopeWholeSubtree,
DerefAliases: LDAP.NeverDerefAliases,
Attributes: attributes,
server.log.Info("Searching for user's groups", "filter", filter)
// support old way of reading settings
groupIDAttribute := server.config.Attr.MemberOf
// but prefer dn attribute if default settings are used
if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
groupIDAttribute = "dn"
}
// Doing a star here to get all the users in one go
Filter: strings.Replace(server.SearchFilter, "%s", "*", -1),
groupSearchReq := ldap.SearchRequest{
BaseDN: groupSearchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
Attributes: []string{groupIDAttribute},
Filter: filter,
}
result, err = ldap.conn.Search(&req)
groupSearchResult, err := server.connection.Search(&groupSearchReq)
if err != nil {
return nil, err
}
if len(result.Entries) > 0 {
if len(groupSearchResult.Entries) > 0 {
for i := range groupSearchResult.Entries {
memberOf = append(memberOf, getLdapAttrN(groupIDAttribute, groupSearchResult, i))
}
break
}
}
return ldap.serializeUsers(result), nil
return memberOf, nil
}
func (ldap *Auth) serializeUsers(users *LDAP.SearchResult) []*UserInfo {
var serialized []*UserInfo
// serializeUsers serializes the users
// from LDAP result to ExternalInfo struct
func (server *Server) serializeUsers(
users *ldap.SearchResult,
) ([]*models.ExternalUserInfo, error) {
var serialized []*models.ExternalUserInfo
for index := range users.Entries {
serialize := &UserInfo{
memberOf, err := server.getMemberOf(users)
if err != nil {
return nil, err
}
userInfo := &UserInfo{
DN: getLdapAttrN(
"dn",
users,
index,
),
LastName: getLdapAttrN(
ldap.server.Attr.Surname,
server.config.Attr.Surname,
users,
index,
),
FirstName: getLdapAttrN(
ldap.server.Attr.Name,
server.config.Attr.Name,
users,
index,
),
Username: getLdapAttrN(
ldap.server.Attr.Username,
server.config.Attr.Username,
users,
index,
),
Email: getLdapAttrN(
ldap.server.Attr.Email,
users,
index,
),
MemberOf: getLdapAttrArrayN(
ldap.server.Attr.MemberOf,
server.config.Attr.Email,
users,
index,
),
MemberOf: memberOf,
}
serialized = append(serialized, serialize)
serialized = append(
serialized,
server.buildGrafanaUser(userInfo),
)
}
return serialized, nil
}
// getMemberOf finds memberOf property or request it
func (server *Server) getMemberOf(search *ldap.SearchResult) (
[]string, error,
) {
if server.config.GroupSearchFilter == "" {
memberOf := getLdapAttrArray(server.config.Attr.MemberOf, search)
return memberOf, nil
}
memberOf, err := server.requestMemberOf(search)
if err != nil {
return nil, err
}
return serialized
return memberOf, nil
}
func appendIfNotEmpty(slice []string, values ...string) []string {
@ -527,11 +545,11 @@ func appendIfNotEmpty(slice []string, values ...string) []string {
return slice
}
func getLdapAttr(name string, result *LDAP.SearchResult) string {
func getLdapAttr(name string, result *ldap.SearchResult) string {
return getLdapAttrN(name, result, 0)
}
func getLdapAttrN(name string, result *LDAP.SearchResult, n int) string {
func getLdapAttrN(name string, result *ldap.SearchResult, n int) string {
if strings.ToLower(name) == "dn" {
return result.Entries[n].DN
}
@ -545,11 +563,11 @@ func getLdapAttrN(name string, result *LDAP.SearchResult, n int) string {
return ""
}
func getLdapAttrArray(name string, result *LDAP.SearchResult) []string {
func getLdapAttrArray(name string, result *ldap.SearchResult) []string {
return getLdapAttrArrayN(name, result, 0)
}
func getLdapAttrArrayN(name string, result *LDAP.SearchResult, n int) []string {
func getLdapAttrArrayN(name string, result *ldap.SearchResult, n int) []string {
for _, attr := range result.Entries[n].Attributes {
if attr.Name == name {
return attr.Values

@ -0,0 +1,140 @@
package ldap
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/infra/log"
)
func TestLDAPHelpers(t *testing.T) {
Convey("serializeUsers()", t, func() {
Convey("simple case", func() {
server := &Server{
config: &ServerConfig{
Attr: AttributeMap{
Username: "username",
Name: "name",
MemberOf: "memberof",
Email: "email",
},
SearchBaseDNs: []string{"BaseDNHere"},
},
connection: &mockConnection{},
log: log.New("test-logger"),
}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "surname", Values: []string{"Gerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
users := &ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
result, err := server.serializeUsers(users)
So(err, ShouldBeNil)
So(result[0].Login, ShouldEqual, "roelgerrits")
So(result[0].Email, ShouldEqual, "roel@test.com")
So(result[0].Groups, ShouldContain, "admins")
})
Convey("without lastname", func() {
server := &Server{
config: &ServerConfig{
Attr: AttributeMap{
Username: "username",
Name: "name",
MemberOf: "memberof",
Email: "email",
},
SearchBaseDNs: []string{"BaseDNHere"},
},
connection: &mockConnection{},
log: log.New("test-logger"),
}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
users := &ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
result, err := server.serializeUsers(users)
So(err, ShouldBeNil)
So(result[0].Name, ShouldEqual, "Roel")
})
})
Convey("serverBind()", t, func() {
Convey("Given bind dn and password configured", func() {
connection := &mockConnection{}
var actualUsername, actualPassword string
connection.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
server := &Server{
connection: connection,
config: &ServerConfig{
BindDN: "o=users,dc=grafana,dc=org",
BindPassword: "bindpwd",
},
}
err := server.serverBind()
So(err, ShouldBeNil)
So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "bindpwd")
})
Convey("Given bind dn configured", func() {
connection := &mockConnection{}
unauthenticatedBindWasCalled := false
var actualUsername string
connection.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
server := &Server{
connection: connection,
config: &ServerConfig{
BindDN: "o=users,dc=grafana,dc=org",
},
}
err := server.serverBind()
So(err, ShouldBeNil)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org")
})
Convey("Given empty bind dn and password", func() {
connection := &mockConnection{}
unauthenticatedBindWasCalled := false
var actualUsername string
connection.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
server := &Server{
connection: connection,
config: &ServerConfig{},
}
err := server.serverBind()
So(err, ShouldBeNil)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldBeEmpty)
})
})
}

@ -7,23 +7,94 @@ import (
"gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/user"
)
func TestLdapLogin(t *testing.T) {
Convey("Login using ldap", t, func() {
AuthScenario("When login with invalid credentials", func(scenario *scenarioContext) {
conn := &mockLdapConn{}
func TestLDAPLogin(t *testing.T) {
Convey("Login()", t, func() {
authScenario("When user is log in and updated", func(sc *scenarioContext) {
// arrange
mockConnection := &mockConnection{}
auth := &Server{
config: &ServerConfig{
Host: "",
RootCACert: "",
Groups: []*GroupToOrgRole{
{GroupDN: "*", OrgRole: "Admin"},
},
Attr: AttributeMap{
Username: "username",
Surname: "surname",
Email: "email",
Name: "name",
MemberOf: "memberof",
},
SearchBaseDNs: []string{"BaseDNHere"},
},
connection: mockConnection,
log: log.New("test-logger"),
}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "surname", Values: []string{"Gerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
mockConnection.setSearchResult(&result)
query := &models.LoginUserQuery{
Username: "roelgerrits",
}
sc.userQueryReturns(&models.User{
Id: 1,
Email: "roel@test.net",
Name: "Roel Gerrits",
Login: "roelgerrits",
})
sc.userOrgsQueryReturns([]*models.UserOrgDTO{})
// act
extUser, _ := auth.Login(query)
userInfo, err := user.Upsert(&user.UpsertArgs{
SignupAllowed: true,
ExternalUser: extUser,
})
// assert
// Check absence of the error
So(err, ShouldBeNil)
// User should be searched in ldap
So(mockConnection.searchCalled, ShouldBeTrue)
// Info should be updated (email differs)
So(userInfo.Email, ShouldEqual, "roel@test.com")
// User should have admin privileges
So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
})
authScenario("When login with invalid credentials", func(scenario *scenarioContext) {
connection := &mockConnection{}
entry := ldap.Entry{}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
conn.setSearchResult(&result)
connection.setSearchResult(&result)
conn.bindProvider = func(username, password string) error {
connection.bindProvider = func(username, password string) error {
return &ldap.Error{
ResultCode: 49,
}
}
auth := &Auth{
server: &ServerConfig{
auth := &Server{
config: &ServerConfig{
Attr: AttributeMap{
Username: "username",
Name: "name",
@ -31,19 +102,19 @@ func TestLdapLogin(t *testing.T) {
},
SearchBaseDNs: []string{"BaseDNHere"},
},
conn: conn,
log: log.New("test-logger"),
connection: connection,
log: log.New("test-logger"),
}
err := auth.Login(scenario.loginUserQuery)
_, err := auth.Login(scenario.loginUserQuery)
Convey("it should return invalid credentials error", func() {
So(err, ShouldEqual, ErrInvalidCredentials)
})
})
AuthScenario("When login with valid credentials", func(scenario *scenarioContext) {
conn := &mockLdapConn{}
authScenario("When login with valid credentials", func(scenario *scenarioContext) {
connection := &mockConnection{}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"markelog"}},
@ -54,13 +125,13 @@ func TestLdapLogin(t *testing.T) {
},
}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
conn.setSearchResult(&result)
connection.setSearchResult(&result)
conn.bindProvider = func(username, password string) error {
connection.bindProvider = func(username, password string) error {
return nil
}
auth := &Auth{
server: &ServerConfig{
auth := &Server{
config: &ServerConfig{
Attr: AttributeMap{
Username: "username",
Name: "name",
@ -68,19 +139,14 @@ func TestLdapLogin(t *testing.T) {
},
SearchBaseDNs: []string{"BaseDNHere"},
},
conn: conn,
log: log.New("test-logger"),
connection: connection,
log: log.New("test-logger"),
}
err := auth.Login(scenario.loginUserQuery)
resp, err := auth.Login(scenario.loginUserQuery)
Convey("it should not return error", func() {
So(err, ShouldBeNil)
})
Convey("it should get user", func() {
So(scenario.loginUserQuery.User.Login, ShouldEqual, "markelog")
})
So(err, ShouldBeNil)
So(resp.Login, ShouldEqual, "markelog")
})
})
}

@ -1,496 +1,157 @@
package ldap
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/ldap.v3"
ldap "gopkg.in/ldap.v3"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
m "github.com/grafana/grafana/pkg/models"
)
func TestAuth(t *testing.T) {
Convey("initialBind", t, func() {
Convey("Given bind dn and password configured", func() {
conn := &mockLdapConn{}
var actualUsername, actualPassword string
conn.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
Auth := &Auth{
conn: conn,
server: &ServerConfig{
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
BindPassword: "bindpwd",
},
}
err := Auth.initialBind("user", "pwd")
So(err, ShouldBeNil)
So(Auth.requireSecondBind, ShouldBeTrue)
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "bindpwd")
})
Convey("Add()", t, func() {
connection := &mockConnection{}
Convey("Given bind dn configured", func() {
conn := &mockLdapConn{}
var actualUsername, actualPassword string
conn.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
Auth := &Auth{
conn: conn,
server: &ServerConfig{
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
},
}
err := Auth.initialBind("user", "pwd")
So(err, ShouldBeNil)
So(Auth.requireSecondBind, ShouldBeFalse)
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "pwd")
})
auth := &Server{
config: &ServerConfig{
SearchBaseDNs: []string{"BaseDNHere"},
},
connection: connection,
log: log.New("test-logger"),
}
Convey("Given empty bind dn and password", func() {
conn := &mockLdapConn{}
unauthenticatedBindWasCalled := false
var actualUsername string
conn.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
Auth := &Auth{
conn: conn,
server: &ServerConfig{},
}
err := Auth.initialBind("user", "pwd")
So(err, ShouldBeNil)
So(Auth.requireSecondBind, ShouldBeTrue)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldBeEmpty)
})
})
Convey("Adds user", func() {
err := auth.Add(
"cn=ldap-tuz,ou=users,dc=grafana,dc=org",
map[string][]string{
"mail": {"ldap-viewer@grafana.com"},
"userPassword": {"grafana"},
"objectClass": {
"person",
"top",
"inetOrgPerson",
"organizationalPerson",
},
"sn": {"ldap-tuz"},
"cn": {"ldap-tuz"},
},
)
hasMail := false
hasUserPassword := false
hasObjectClass := false
hasSN := false
hasCN := false
Convey("serverBind", t, func() {
Convey("Given bind dn and password configured", func() {
conn := &mockLdapConn{}
var actualUsername, actualPassword string
conn.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
Auth := &Auth{
conn: conn,
server: &ServerConfig{
BindDN: "o=users,dc=grafana,dc=org",
BindPassword: "bindpwd",
},
}
err := Auth.serverBind()
So(err, ShouldBeNil)
So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "bindpwd")
})
Convey("Given bind dn configured", func() {
conn := &mockLdapConn{}
unauthenticatedBindWasCalled := false
var actualUsername string
conn.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
Auth := &Auth{
conn: conn,
server: &ServerConfig{
BindDN: "o=users,dc=grafana,dc=org",
},
So(connection.addParams.Controls, ShouldBeNil)
So(connection.addCalled, ShouldBeTrue)
So(
connection.addParams.DN,
ShouldEqual,
"cn=ldap-tuz,ou=users,dc=grafana,dc=org",
)
attrs := connection.addParams.Attributes
for _, value := range attrs {
if value.Type == "mail" {
So(value.Vals, ShouldContain, "ldap-viewer@grafana.com")
hasMail = true
}
if value.Type == "userPassword" {
hasUserPassword = true
So(value.Vals, ShouldContain, "grafana")
}
if value.Type == "objectClass" {
hasObjectClass = true
So(value.Vals, ShouldContain, "person")
So(value.Vals, ShouldContain, "top")
So(value.Vals, ShouldContain, "inetOrgPerson")
So(value.Vals, ShouldContain, "organizationalPerson")
}
if value.Type == "sn" {
hasSN = true
So(value.Vals, ShouldContain, "ldap-tuz")
}
if value.Type == "cn" {
hasCN = true
So(value.Vals, ShouldContain, "ldap-tuz")
}
}
err := Auth.serverBind()
So(err, ShouldBeNil)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org")
})
Convey("Given empty bind dn and password", func() {
conn := &mockLdapConn{}
unauthenticatedBindWasCalled := false
var actualUsername string
conn.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
Auth := &Auth{
conn: conn,
server: &ServerConfig{},
}
err := Auth.serverBind()
So(err, ShouldBeNil)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldBeEmpty)
So(hasMail, ShouldBeTrue)
So(hasUserPassword, ShouldBeTrue)
So(hasObjectClass, ShouldBeTrue)
So(hasSN, ShouldBeTrue)
So(hasCN, ShouldBeTrue)
})
})
Convey("When translating ldap user to grafana user", t, func() {
Convey("Remove()", t, func() {
connection := &mockConnection{}
var user1 = &m.User{}
bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpsertUserCommand) error {
cmd.Result = user1
cmd.Result.Login = "torkelo"
return nil
})
Convey("Given no ldap group map match", func() {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{{}},
})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{})
So(err, ShouldEqual, ErrInvalidCredentials)
})
AuthScenario("Given wildcard group match", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "*", OrgRole: "Admin"},
},
})
sc.userQueryReturns(user1)
result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{})
So(err, ShouldBeNil)
So(result, ShouldEqual, user1)
})
AuthScenario("Given exact group match", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=users", OrgRole: "Admin"},
},
})
sc.userQueryReturns(user1)
result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{MemberOf: []string{"cn=users"}})
So(err, ShouldBeNil)
So(result, ShouldEqual, user1)
})
AuthScenario("Given group match with different case", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=users", OrgRole: "Admin"},
},
})
sc.userQueryReturns(user1)
result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{MemberOf: []string{"CN=users"}})
So(err, ShouldBeNil)
So(result, ShouldEqual, user1)
})
AuthScenario("Given no existing grafana user", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=admin", OrgRole: "Admin"},
{GroupDN: "cn=editor", OrgRole: "Editor"},
{GroupDN: "*", OrgRole: "Viewer"},
},
})
sc.userQueryReturns(nil)
result, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
DN: "torkelo",
Username: "torkelo",
Email: "my@email.com",
MemberOf: []string{"cn=editor"},
})
So(err, ShouldBeNil)
Convey("Should return new user", func() {
So(result.Login, ShouldEqual, "torkelo")
})
Convey("Should set isGrafanaAdmin to false by default", func() {
So(result.IsAdmin, ShouldBeFalse)
})
})
})
Convey("When syncing ldap groups to grafana org roles", t, func() {
AuthScenario("given no current user orgs", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=users", OrgRole: "Admin"},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=users"},
})
Convey("Should create new org user", func() {
So(err, ShouldBeNil)
So(sc.addOrgUserCmd, ShouldNotBeNil)
So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
})
})
AuthScenario("given different current org role", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=users"},
})
Convey("Should update org role", func() {
So(err, ShouldBeNil)
So(sc.updateOrgUserCmd, ShouldNotBeNil)
So(sc.updateOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
})
})
AuthScenario("given current org role is removed in ldap", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=users", OrgId: 2, OrgRole: "Admin"},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{
{OrgId: 1, Role: m.ROLE_EDITOR},
{OrgId: 2, Role: m.ROLE_EDITOR},
})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=users"},
})
Convey("Should remove org role", func() {
So(err, ShouldBeNil)
So(sc.removeOrgUserCmd, ShouldNotBeNil)
So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 2)
})
})
AuthScenario("given org role is updated in config", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=admin", OrgId: 1, OrgRole: "Admin"},
{GroupDN: "cn=users", OrgId: 1, OrgRole: "Viewer"},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=users"},
})
Convey("Should update org role", func() {
So(err, ShouldBeNil)
So(sc.removeOrgUserCmd, ShouldBeNil)
So(sc.updateOrgUserCmd, ShouldNotBeNil)
So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
})
})
AuthScenario("given multiple matching ldap groups", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
{GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=admins"},
})
Convey("Should take first match, and ignore subsequent matches", func() {
So(err, ShouldBeNil)
So(sc.updateOrgUserCmd, ShouldBeNil)
So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
})
})
AuthScenario("given multiple matching ldap groups and no existing groups", func(sc *scenarioContext) {
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
{GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=admins"},
})
Convey("Should take first match, and ignore subsequent matches", func() {
So(err, ShouldBeNil)
So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
})
Convey("Should not update permissions unless specified", func() {
So(err, ShouldBeNil)
So(sc.updateUserPermissionsCmd, ShouldBeNil)
})
})
AuthScenario("given ldap groups with grafana_admin=true", func(sc *scenarioContext) {
trueVal := true
Auth := New(&ServerConfig{
Groups: []*GroupToOrgRole{
{GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin", IsGrafanaAdmin: &trueVal},
},
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
_, err := Auth.GetGrafanaUserFor(nil, &UserInfo{
MemberOf: []string{"cn=admins"},
})
Convey("Should create user with admin set to true", func() {
So(err, ShouldBeNil)
So(sc.updateUserPermissionsCmd.IsGrafanaAdmin, ShouldBeTrue)
})
})
})
Convey("When calling SyncUser", t, func() {
mockLdapConnection := &mockLdapConn{}
auth := &Auth{
server: &ServerConfig{
Host: "",
RootCACert: "",
Groups: []*GroupToOrgRole{
{GroupDN: "*", OrgRole: "Admin"},
},
Attr: AttributeMap{
Username: "username",
Surname: "surname",
Email: "email",
Name: "name",
MemberOf: "memberof",
},
auth := &Server{
config: &ServerConfig{
SearchBaseDNs: []string{"BaseDNHere"},
},
conn: mockLdapConnection,
log: log.New("test-logger"),
}
dialCalled := false
dial = func(network, addr string) (IConnection, error) {
dialCalled = true
return mockLdapConnection, nil
connection: connection,
log: log.New("test-logger"),
}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "surname", Values: []string{"Gerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
mockLdapConnection.setSearchResult(&result)
Convey("Removes the user", func() {
dn := "cn=ldap-tuz,ou=users,dc=grafana,dc=org"
err := auth.Remove(dn)
AuthScenario("When ldapUser found call syncInfo and orgRoles", func(sc *scenarioContext) {
// arrange
query := &m.LoginUserQuery{
Username: "roelgerrits",
}
hookDial = nil
sc.userQueryReturns(&m.User{
Id: 1,
Email: "roel@test.net",
Name: "Roel Gerrits",
Login: "roelgerrits",
})
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
// act
syncErrResult := auth.SyncUser(query)
// assert
So(dialCalled, ShouldBeTrue)
So(syncErrResult, ShouldBeNil)
// User should be searched in ldap
So(mockLdapConnection.searchCalled, ShouldBeTrue)
// Info should be updated (email differs)
So(sc.updateUserCmd.Email, ShouldEqual, "roel@test.com")
// User should have admin privileges
So(sc.addOrgUserCmd.UserId, ShouldEqual, 1)
So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
So(err, ShouldBeNil)
So(connection.delCalled, ShouldBeTrue)
So(connection.delParams.Controls, ShouldBeNil)
So(connection.delParams.DN, ShouldEqual, dn)
})
})
Convey("When searching for a user and not all five attributes are mapped", t, func() {
mockLdapConnection := &mockLdapConn{}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "surname", Values: []string{"Gerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
mockLdapConnection.setSearchResult(&result)
// Set up attribute map without surname and email
Auth := &Auth{
server: &ServerConfig{
Attr: AttributeMap{
Username: "username",
Name: "name",
MemberOf: "memberof",
},
SearchBaseDNs: []string{"BaseDNHere"},
},
conn: mockLdapConnection,
log: log.New("test-logger"),
}
Convey("Users()", t, func() {
Convey("find one user", func() {
mockConnection := &mockConnection{}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "surname", Values: []string{"Gerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
mockConnection.setSearchResult(&result)
// Set up attribute map without surname and email
server := &Server{
config: &ServerConfig{
Attr: AttributeMap{
Username: "username",
Name: "name",
MemberOf: "memberof",
},
SearchBaseDNs: []string{"BaseDNHere"},
},
connection: mockConnection,
log: log.New("test-logger"),
}
searchResult, err := Auth.searchForUser("roelgerrits")
searchResult, err := server.Users([]string{"roelgerrits"})
So(err, ShouldBeNil)
So(searchResult, ShouldNotBeNil)
So(err, ShouldBeNil)
So(searchResult, ShouldNotBeNil)
// User should be searched in ldap
So(mockLdapConnection.searchCalled, ShouldBeTrue)
// User should be searched in ldap
So(mockConnection.searchCalled, ShouldBeTrue)
// No empty attributes should be added to the search request
So(len(mockLdapConnection.searchAttributes), ShouldEqual, 3)
// No empty attributes should be added to the search request
So(len(mockConnection.searchAttributes), ShouldEqual, 3)
})
})
}

@ -13,10 +13,12 @@ import (
"github.com/grafana/grafana/pkg/util/errutil"
)
// Config holds list of connections to LDAP
type Config struct {
Servers []*ServerConfig `toml:"servers"`
}
// ServerConfig holds connection data to LDAP
type ServerConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
@ -108,11 +110,11 @@ func readConfig(configFile string) (*Config, error) {
_, err := toml.DecodeFile(configFile, result)
if err != nil {
return nil, errutil.Wrap("Failed to load ldap config file", err)
return nil, errutil.Wrap("Failed to load LDAP config file", err)
}
if len(result.Servers) == 0 {
return nil, xerrors.New("ldap enabled but no ldap servers defined in config file")
return nil, xerrors.New("LDAP enabled but no LDAP servers defined in config file")
}
// set default org id

@ -12,15 +12,22 @@ import (
"github.com/grafana/grafana/pkg/services/login"
)
type mockLdapConn struct {
result *ldap.SearchResult
searchCalled bool
searchAttributes []string
type mockConnection struct {
searchResult *ldap.SearchResult
searchCalled bool
searchAttributes []string
addParams *ldap.AddRequest
addCalled bool
delParams *ldap.DelRequest
delCalled bool
bindProvider func(username, password string) error
unauthenticatedBindProvider func(username string) error
}
func (c *mockLdapConn) Bind(username, password string) error {
func (c *mockConnection) Bind(username, password string) error {
if c.bindProvider != nil {
return c.bindProvider(username, password)
}
@ -28,7 +35,7 @@ func (c *mockLdapConn) Bind(username, password string) error {
return nil
}
func (c *mockLdapConn) UnauthenticatedBind(username string) error {
func (c *mockConnection) UnauthenticatedBind(username string) error {
if c.unauthenticatedBindProvider != nil {
return c.unauthenticatedBindProvider(username)
}
@ -36,23 +43,35 @@ func (c *mockLdapConn) UnauthenticatedBind(username string) error {
return nil
}
func (c *mockLdapConn) Close() {}
func (c *mockConnection) Close() {}
func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) {
c.result = result
func (c *mockConnection) setSearchResult(result *ldap.SearchResult) {
c.searchResult = result
}
func (c *mockLdapConn) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) {
func (c *mockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) {
c.searchCalled = true
c.searchAttributes = sr.Attributes
return c.result, nil
return c.searchResult, nil
}
func (c *mockConnection) Add(request *ldap.AddRequest) error {
c.addCalled = true
c.addParams = request
return nil
}
func (c *mockLdapConn) StartTLS(*tls.Config) error {
func (c *mockConnection) Del(request *ldap.DelRequest) error {
c.delCalled = true
c.delParams = request
return nil
}
func AuthScenario(desc string, fn scenarioFunc) {
func (c *mockConnection) StartTLS(*tls.Config) error {
return nil
}
func authScenario(desc string, fn scenarioFunc) {
Convey(desc, func() {
defer bus.ClearBusHandlers()
@ -64,10 +83,6 @@ func AuthScenario(desc string, fn scenarioFunc) {
},
}
hookDial = func(auth *Auth) error {
return nil
}
loginService := &login.LoginService{
Bus: bus.GetBus(),
}

@ -0,0 +1,204 @@
package multildap
import (
"errors"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
)
// GetConfig gets LDAP config
var GetConfig = ldap.GetConfig
// IsEnabled checks if LDAP is enabled
var IsEnabled = ldap.IsEnabled
// ErrInvalidCredentials is returned if username and password do not match
var ErrInvalidCredentials = ldap.ErrInvalidCredentials
// ErrNoLDAPServers is returned when there is no LDAP servers specified
var ErrNoLDAPServers = errors.New("No LDAP servers are configured")
// ErrDidNotFindUser if request for user is unsuccessful
var ErrDidNotFindUser = errors.New("Did not find a user")
// IMultiLDAP is interface for MultiLDAP
type IMultiLDAP interface {
Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo, error,
)
Users(logins []string) (
[]*models.ExternalUserInfo, error,
)
User(login string) (
*models.ExternalUserInfo, error,
)
Add(dn string, values map[string][]string) error
Remove(dn string) error
}
// MultiLDAP is basic struct of LDAP authorization
type MultiLDAP struct {
configs []*ldap.ServerConfig
}
// New creates the new LDAP auth
func New(configs []*ldap.ServerConfig) IMultiLDAP {
return &MultiLDAP{
configs: configs,
}
}
// Add adds user to the *first* defined LDAP
func (multiples *MultiLDAP) Add(
dn string,
values map[string][]string,
) error {
if len(multiples.configs) == 0 {
return ErrNoLDAPServers
}
config := multiples.configs[0]
ldap := ldap.New(config)
if err := ldap.Dial(); err != nil {
return err
}
defer ldap.Close()
err := ldap.Add(dn, values)
if err != nil {
return err
}
return nil
}
// Remove removes user from the *first* defined LDAP
func (multiples *MultiLDAP) Remove(dn string) error {
if len(multiples.configs) == 0 {
return ErrNoLDAPServers
}
config := multiples.configs[0]
ldap := ldap.New(config)
if err := ldap.Dial(); err != nil {
return err
}
defer ldap.Close()
err := ldap.Remove(dn)
if err != nil {
return err
}
return nil
}
// Login tries to log in the user in multiples LDAP
func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo, error,
) {
if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers
}
for _, config := range multiples.configs {
server := ldap.New(config)
if err := server.Dial(); err != nil {
return nil, err
}
defer server.Close()
user, err := server.Login(query)
if user != nil {
return user, nil
}
// Continue if we couldn't find the user
if err == ErrInvalidCredentials {
continue
}
if err != nil {
return nil, err
}
return user, nil
}
// Return invalid credentials if we couldn't find the user anywhere
return nil, ErrInvalidCredentials
}
// User gets a user by login
func (multiples *MultiLDAP) User(login string) (
*models.ExternalUserInfo,
error,
) {
if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers
}
search := []string{login}
for _, config := range multiples.configs {
server := ldap.New(config)
if err := server.Dial(); err != nil {
return nil, err
}
defer server.Close()
users, err := server.Users(search)
if err != nil {
return nil, err
}
if len(users) != 0 {
return users[0], nil
}
}
return nil, ErrDidNotFindUser
}
// Users gets users from multiple LDAP servers
func (multiples *MultiLDAP) Users(logins []string) (
[]*models.ExternalUserInfo,
error,
) {
var result []*models.ExternalUserInfo
if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers
}
for _, config := range multiples.configs {
server := ldap.New(config)
if err := server.Dial(); err != nil {
return nil, err
}
defer server.Close()
users, err := server.Users(logins)
if err != nil {
return nil, err
}
result = append(result, users...)
}
return result, nil
}

@ -8,7 +8,6 @@ import (
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/go-sql-driver/mysql"
@ -280,7 +279,14 @@ func (ss *SqlStore) readConfig() {
ss.dbCfg.CacheMode = sec.Key("cache_mode").MustString("private")
}
func InitTestDB(t *testing.T) *SqlStore {
// Interface of arguments for testing db
type ITestDB interface {
Helper()
Fatalf(format string, args ...interface{})
}
// InitTestDB initiliaze test DB
func InitTestDB(t ITestDB) *SqlStore {
t.Helper()
sqlstore := &SqlStore{}
sqlstore.skipEnsureAdmin = true

@ -0,0 +1,39 @@
package user
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
)
// UpsertArgs are object for Upsert method
type UpsertArgs struct {
ReqContext *models.ReqContext
ExternalUser *models.ExternalUserInfo
SignupAllowed bool
}
// Upsert add/update grafana user
func Upsert(args *UpsertArgs) (*models.User, error) {
query := &models.UpsertUserCommand{
ReqContext: args.ReqContext,
ExternalUser: args.ExternalUser,
SignupAllowed: args.SignupAllowed,
}
err := bus.Dispatch(query)
if err != nil {
return nil, err
}
return query.Result, nil
}
// Get the users
func Get(
query *models.SearchUsersQuery,
) ([]*models.UserSearchHitDTO, error) {
if err := bus.Dispatch(query); err != nil {
return nil, err
}
return query.Result.Users, nil
}

@ -805,6 +805,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
// auth proxy
authProxy := iniFile.Section("auth.proxy")
AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
AuthProxyHeaderName, err = valueAsString(authProxy, "header_name", "")
if err != nil {
return err

@ -0,0 +1,134 @@
go test -bench=. -benchmem
goos: darwin
goarch: amd64
pkg: github.com/brianvoe/gofakeit
Table generated with tablesgenerator.com/markdown_tables
| Benchmark | Ops | CPU | MEM | MEM alloc |
|---------------------------------|-----------|-------------|------------|--------------|
| BenchmarkAddress-4 | 1000000 | 1998 ns/op | 248 B/op | 7 allocs/op |
| BenchmarkStreet-4 | 1000000 | 1278 ns/op | 62 B/op | 3 allocs/op |
| BenchmarkStreetNumber-4 | 5000000 | 344 ns/op | 36 B/op | 2 allocs/op |
| BenchmarkStreetPrefix-4 | 10000000 | 121 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkStreetName-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkStreetSuffix-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkCity-4 | 5000000 | 326 ns/op | 15 B/op | 1 allocs/op |
| BenchmarkState-4 | 10000000 | 120 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkStateAbr-4 | 10000000 | 122 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkZip-4 | 5000000 | 315 ns/op | 5 B/op | 1 allocs/op |
| BenchmarkCountry-4 | 10000000 | 126 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkCountryAbr-4 | 10000000 | 123 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLatitude-4 | 100000000 | 23.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLongitude-4 | 100000000 | 23.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLatitudeInRange-4 | 50000000 | 27.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLongitudeInRange-4 | 50000000 | 27.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBeerName-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBeerStyle-4 | 10000000 | 119 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBeerHop-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBeerYeast-4 | 20000000 | 106 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBeerMalt-4 | 20000000 | 114 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBeerIbu-4 | 20000000 | 71.0 ns/op | 8 B/op | 1 allocs/op |
| BenchmarkBeerAlcohol-4 | 5000000 | 335 ns/op | 40 B/op | 3 allocs/op |
| BenchmarkBeerBlg-4 | 5000000 | 338 ns/op | 48 B/op | 3 allocs/op |
| BenchmarkBool-4 | 50000000 | 34.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkColor-4 | 20000000 | 112 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSafeColor-4 | 20000000 | 102 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHexColor-4 | 3000000 | 491 ns/op | 24 B/op | 3 allocs/op |
| BenchmarkRGBColor-4 | 20000000 | 103 ns/op | 32 B/op | 1 allocs/op |
| BenchmarkCompany-4 | 5000000 | 353 ns/op | 22 B/op | 1 allocs/op |
| BenchmarkCompanySuffix-4 | 20000000 | 89.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBuzzWord-4 | 20000000 | 99.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkBS-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContact-4 | 1000000 | 1121 ns/op | 178 B/op | 7 allocs/op |
| BenchmarkPhone-4 | 5000000 | 346 ns/op | 16 B/op | 1 allocs/op |
| BenchmarkPhoneFormatted-4 | 3000000 | 456 ns/op | 16 B/op | 1 allocs/op |
| BenchmarkEmail-4 | 2000000 | 715 ns/op | 130 B/op | 5 allocs/op |
| BenchmarkCurrency-4 | 10000000 | 125 ns/op | 32 B/op | 1 allocs/op |
| BenchmarkCurrencyShort-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkCurrencyLong-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkPrice-4 | 50000000 | 27.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkDate-4 | 5000000 | 371 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkDateRange-4 | 10000000 | 238 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMonth-4 | 30000000 | 44.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkDay-4 | 50000000 | 39.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkWeekDay-4 | 30000000 | 44.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkYear-4 | 20000000 | 115 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHour-4 | 30000000 | 39.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinute-4 | 50000000 | 40.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSecond-4 | 30000000 | 40.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkNanoSecond-4 | 30000000 | 42.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkTimeZone-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkTimeZoneFull-4 | 20000000 | 118 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkTimeZoneAbv-4 | 20000000 | 105 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkTimeZoneOffset-4 | 10000000 | 147 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMimeType-4 | 20000000 | 99.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkExtension-4 | 20000000 | 109 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkGenerate-4 | 1000000 | 1588 ns/op | 414 B/op | 11 allocs/op |
| BenchmarkHackerPhrase-4 | 300000 | 4576 ns/op | 2295 B/op | 26 allocs/op |
| BenchmarkHackerAbbreviation-4 | 20000000 | 101 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHackerAdjective-4 | 20000000 | 101 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHackerNoun-4 | 20000000 | 104 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHackerVerb-4 | 20000000 | 113 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHackerIngverb-4 | 20000000 | 98.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHipsterWord-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkHipsterSentence-4 | 1000000 | 1636 ns/op | 353 B/op | 3 allocs/op |
| BenchmarkHipsterParagraph-4 | 50000 | 31677 ns/op | 12351 B/op | 64 allocs/op |
| BenchmarkImageURL-4 | 20000000 | 108 ns/op | 38 B/op | 3 allocs/op |
| BenchmarkDomainName-4 | 3000000 | 491 ns/op | 76 B/op | 3 allocs/op |
| BenchmarkDomainSuffix-4 | 20000000 | 99.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkURL-4 | 1000000 | 1201 ns/op | 278 B/op | 8 allocs/op |
| BenchmarkHTTPMethod-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkIPv4Address-4 | 3000000 | 407 ns/op | 48 B/op | 5 allocs/op |
| BenchmarkIPv6Address-4 | 3000000 | 552 ns/op | 96 B/op | 7 allocs/op |
| BenchmarkUsername-4 | 5000000 | 307 ns/op | 16 B/op | 2 allocs/op |
| BenchmarkJob-4 | 2000000 | 726 ns/op | 86 B/op | 2 allocs/op |
| BenchmarkJobTitle-4 | 20000000 | 98.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkJobDescriptor-4 | 20000000 | 98.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkJobLevel-4 | 20000000 | 110 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLogLevel-4 | 20000000 | 107 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkReplaceWithNumbers-4 | 3000000 | 570 ns/op | 32 B/op | 1 allocs/op |
| BenchmarkName-4 | 5000000 | 285 ns/op | 17 B/op | 1 allocs/op |
| BenchmarkFirstName-4 | 20000000 | 102 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLastName-4 | 20000000 | 100 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkNamePrefix-4 | 20000000 | 98.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkNameSuffix-4 | 20000000 | 109 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkNumber-4 | 50000000 | 34.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkUint8-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkUint16-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkUint32-4 | 50000000 | 27.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkUint64-4 | 50000000 | 34.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkInt8-4 | 50000000 | 28.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkInt16-4 | 50000000 | 28.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkInt32-4 | 50000000 | 27.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkInt64-4 | 50000000 | 34.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkFloat32-4 | 50000000 | 27.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkFloat32Range-4 | 50000000 | 27.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkFloat64-4 | 50000000 | 25.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkFloat64Range-4 | 50000000 | 26.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkNumerify-4 | 5000000 | 354 ns/op | 16 B/op | 1 allocs/op |
| BenchmarkShuffleInts-4 | 10000000 | 226 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkPassword-4 | 2000000 | 655 ns/op | 304 B/op | 6 allocs/op |
| BenchmarkCreditCard-4 | 2000000 | 997 ns/op | 88 B/op | 4 allocs/op |
| BenchmarkCreditCardType-4 | 20000000 | 92.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkCreditCardNumber-4 | 3000000 | 572 ns/op | 16 B/op | 1 allocs/op |
| BenchmarkCreditCardNumberLuhn-4 | 300000 | 5815 ns/op | 159 B/op | 9 allocs/op |
| BenchmarkCreditCardExp-4 | 10000000 | 129 ns/op | 5 B/op | 1 allocs/op |
| BenchmarkCreditCardCvv-4 | 10000000 | 128 ns/op | 3 B/op | 1 allocs/op |
| BenchmarkSSN-4 | 20000000 | 84.2 ns/op | 16 B/op | 1 allocs/op |
| BenchmarkGender-4 | 50000000 | 38.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkPerson-4 | 300000 | 5563 ns/op | 805 B/op | 26 allocs/op |
| BenchmarkSimpleStatusCode-4 | 20000000 | 72.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkStatusCode-4 | 20000000 | 75.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLetter-4 | 50000000 | 38.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkDigit-4 | 50000000 | 38.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkLexify-4 | 10000000 | 222 ns/op | 8 B/op | 1 allocs/op |
| BenchmarkShuffleStrings-4 | 10000000 | 197 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkUUID-4 | 20000000 | 106 ns/op | 48 B/op | 1 allocs/op |
| BenchmarkUserAgent-4 | 1000000 | 1236 ns/op | 305 B/op | 5 allocs/op |
| BenchmarkChromeUserAgent-4 | 2000000 | 881 ns/op | 188 B/op | 5 allocs/op |
| BenchmarkFirefoxUserAgent-4 | 1000000 | 1595 ns/op | 386 B/op | 7 allocs/op |
| BenchmarkSafariUserAgent-4 | 1000000 | 1396 ns/op | 551 B/op | 7 allocs/op |
| BenchmarkOperaUserAgent-4 | 2000000 | 950 ns/op | 216 B/op | 5 allocs/op |
| BenchmarkWord-4 | 20000000 | 99.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSentence-4 | 1000000 | 1540 ns/op | 277 B/op | 2 allocs/op |
| BenchmarkParagraph-4 | 50000 | 30978 ns/op | 11006 B/op | 61 allocs/op |

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at brian@webiswhatido.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

@ -0,0 +1 @@
# Make a pull request and submit it and ill take a look at it. Thanks!

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,254 @@
![alt text](https://raw.githubusercontent.com/brianvoe/gofakeit/master/logo.png)
# gofakeit [![Go Report Card](https://goreportcard.com/badge/github.com/brianvoe/gofakeit)](https://goreportcard.com/report/github.com/brianvoe/gofakeit) [![Build Status](https://travis-ci.org/brianvoe/gofakeit.svg?branch=master)](https://travis-ci.org/brianvoe/gofakeit) [![codecov.io](https://codecov.io/github/brianvoe/gofakeit/branch/master/graph/badge.svg)](https://codecov.io/github/brianvoe/gofakeit) [![GoDoc](https://godoc.org/github.com/brianvoe/gofakeit?status.svg)](https://godoc.org/github.com/brianvoe/gofakeit) [![license](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/brianvoe/gofakeit/master/LICENSE.txt)
Random data generator written in go
<a href="https://www.buymeacoffee.com/brianvoe" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
### Features
- Every function has an example and a benchmark,
[see benchmarks](https://github.com/brianvoe/gofakeit/blob/master/BENCHMARKS.md)
- Zero dependencies
- Randomizes user defined structs
- Numerous functions for regular use
### 120+ Functions!!!
If there is something that is generic enough missing from this package [add an issue](https://github.com/brianvoe/gofakeit/issues) and let me know what you need.
Most of the time i'll add it!
## Person
```go
Person() *PersonInfo
Name() string
NamePrefix() string
NameSuffix() string
FirstName() string
LastName() string
Gender() string
SSN() string
Contact() *ContactInfo
Email() string
Phone() string
PhoneFormatted() string
Username() string
Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string
```
## Address
```go
Address() *AddressInfo
City() string
Country() string
CountryAbr() string
State() string
StateAbr() string
StatusCode() string
Street() string
StreetName() string
StreetNumber() string
StreetPrefix() string
StreetSuffix() string
Zip() string
Latitude() float64
LatitudeInRange() (float64, error)
Longitude() float64
LongitudeInRange() (float64, error)
```
## Beer
```go
BeerAlcohol() string
BeerBlg() string
BeerHop() string
BeerIbu() string
BeerMalt() string
BeerName() string
BeerStyle() string
BeerYeast() string
```
## Cars
```go
Vehicle() *VehicleInfo
CarMaker() string
CarModel() string
VehicleType() string
FuelType() string
TransmissionGearType() string
```
## Words
```go
Word() string
Sentence(wordCount int) string
Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string
Question() string
Quote() string
```
## Misc
```go
Struct(v interface{})
Generate() string
Bool() bool
UUID() string
```
## Colors
```go
Color() string
HexColor() string
RGBColor() string
SafeColor() string
```
## Internet
```go
URL() string
ImageURL(width int, height int) string
DomainName() string
DomainSuffix() string
IPv4Address() string
IPv6Address() string
SimpleStatusCode() int
LogLevel(logType string) string
HTTPMethod() string
UserAgent() string
ChromeUserAgent() string
FirefoxUserAgent() string
OperaUserAgent() string
SafariUserAgent() string
```
## Date/Time
```go
Date() time.Time
DateRange(start, end time.Time) time.Time
NanoSecond() int
Second() int
Minute() int
Hour() int
Month() string
Day() int
WeekDay() string
Year() int
TimeZone() string
TimeZoneAbv() string
TimeZoneFull() string
TimeZoneOffset() float32
```
## Payment
```go
Price(min, max float64) float64
CreditCard() *CreditCardInfo
CreditCardCvv() string
CreditCardExp() string
CreditCardNumber() int
CreditCardNumberLuhn() int
CreditCardType() string
Currency() *CurrencyInfo
CurrencyLong() string
CurrencyShort() string
```
## Company
```go
BS() string
BuzzWord() string
Company() string
CompanySuffix() string
Job() *JobInfo
JobDescriptor() string
JobLevel() string
JobTitle() string
```
## Hacker
```go
HackerAbbreviation() string
HackerAdjective() string
HackerIngverb() string
HackerNoun() string
HackerPhrase() string
HackerVerb() string
```
## Hipster
```go
HipsterWord() string
HipsterSentence(wordCount int) string
HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string
```
## File
```go
Extension() string
MimeType() string
```
## Numbers
```go
Number(min int, max int) int
Numerify(str string) string
Int8() int8
Int16() int16
Int32() int32
Int64() int64
Uint8() uint8
Uint16() uint16
Uint32() uint32
Uint64() uint64
Float32() float32
Float32Range(min, max float32) float32
Float64() float64
Float64Range(min, max float64) float64
ShuffleInts(a []int)
```
## String
```go
Digit() string
Letter() string
Lexify(str string) string
RandString(a []string) string
ShuffleStrings(a []string)
```
## Documentation
[![GoDoc](https://godoc.org/github.com/brianvoe/gofakeit?status.svg)](https://godoc.org/github.com/brianvoe/gofakeit)
## Example
```go
import "github.com/brianvoe/gofakeit"
gofakeit.Name() // Markus Moen
gofakeit.Email() // alaynawuckert@kozey.biz
gofakeit.Phone() // (570)245-7485
gofakeit.BS() // front-end
gofakeit.BeerName() // Duvel
gofakeit.Color() // MediumOrchid
gofakeit.Company() // Moen, Pagac and Wuckert
gofakeit.CreditCardNumber() // 4287271570245748
gofakeit.HackerPhrase() // Connecting the array won't do anything, we need to generate the haptic COM driver!
gofakeit.JobTitle() // Director
gofakeit.Password(true, true, true, true, true, 32) // WV10MzLxq2DX79w1omH97_0ga59j8!kj
gofakeit.CurrencyShort() // USD
// 120+ more!!!
// Create structs with random injected data
type Foo struct {
Bar string
Baz string
Int int
Pointer *int
Skip *string `fake:"skip"` // Set to "skip" to not generate data for
}
var f Foo
gofakeit.Struct(&f)
fmt.Printf("f.Bar:%s\n", f.Bar) // f.Bar:hrukpttuezptneuvunh
fmt.Printf("f.Baz:%s\n", f.Baz) // f.Baz:uksqvgzadxlgghejkmv
fmt.Printf("f.Int:%d\n", f.Int) // f.Int:-7825289004089916589
fmt.Printf("f.Pointer:%d\n", *f.Pointer) // f.Pointer:-343806609094473732
fmt.Printf("f.Skip:%v\n", f.Skip) // f.Skip:<nil>
```

@ -0,0 +1,3 @@
* Take a look at [chance.js](http://chancejs.com/) and see if i missed anything.
* Look into [National Baby Name List](http://www.ssa.gov/oact/babynames/limits.html) and see if that makes sense to replace over what we currently have.
* Look at [data list](https://github.com/dariusk/corpora/tree/master/data) and see if it makes sense to add that data in or if it seems unncessary.

@ -0,0 +1,131 @@
package gofakeit
import (
"errors"
"math/rand"
"strings"
)
// AddressInfo is a struct full of address information
type AddressInfo struct {
Address string
Street string
City string
State string
Zip string
Country string
Latitude float64
Longitude float64
}
// Address will generate a struct of address information
func Address() *AddressInfo {
street := Street()
city := City()
state := State()
zip := Zip()
return &AddressInfo{
Address: street + ", " + city + ", " + state + " " + zip,
Street: street,
City: city,
State: state,
Zip: zip,
Country: Country(),
Latitude: Latitude(),
Longitude: Longitude(),
}
}
// Street will generate a random address street string
func Street() (street string) {
switch randInt := randIntRange(1, 2); randInt {
case 1:
street = StreetNumber() + " " + StreetPrefix() + " " + StreetName() + StreetSuffix()
case 2:
street = StreetNumber() + " " + StreetName() + StreetSuffix()
}
return
}
// StreetNumber will generate a random address street number string
func StreetNumber() string {
return strings.TrimLeft(replaceWithNumbers(getRandValue([]string{"address", "number"})), "0")
}
// StreetPrefix will generate a random address street prefix string
func StreetPrefix() string {
return getRandValue([]string{"address", "street_prefix"})
}
// StreetName will generate a random address street name string
func StreetName() string {
return getRandValue([]string{"address", "street_name"})
}
// StreetSuffix will generate a random address street suffix string
func StreetSuffix() string {
return getRandValue([]string{"address", "street_suffix"})
}
// City will generate a random city string
func City() (city string) {
switch randInt := randIntRange(1, 3); randInt {
case 1:
city = FirstName() + StreetSuffix()
case 2:
city = LastName() + StreetSuffix()
case 3:
city = StreetPrefix() + " " + LastName()
}
return
}
// State will generate a random state string
func State() string {
return getRandValue([]string{"address", "state"})
}
// StateAbr will generate a random abbreviated state string
func StateAbr() string {
return getRandValue([]string{"address", "state_abr"})
}
// Zip will generate a random Zip code string
func Zip() string {
return replaceWithNumbers(getRandValue([]string{"address", "zip"}))
}
// Country will generate a random country string
func Country() string {
return getRandValue([]string{"address", "country"})
}
// CountryAbr will generate a random abbreviated country string
func CountryAbr() string {
return getRandValue([]string{"address", "country_abr"})
}
// Latitude will generate a random latitude float64
func Latitude() float64 { return (rand.Float64() * 180) - 90 }
// LatitudeInRange will generate a random latitude within the input range
func LatitudeInRange(min, max float64) (float64, error) {
if min > max || min < -90 || min > 90 || max < -90 || max > 90 {
return 0, errors.New("input range is invalid")
}
return randFloat64Range(min, max), nil
}
// Longitude will generate a random longitude float64
func Longitude() float64 { return (rand.Float64() * 360) - 180 }
// LongitudeInRange will generate a random longitude within the input range
func LongitudeInRange(min, max float64) (float64, error) {
if min > max || min < -180 || min > 180 || max < -180 || max > 180 {
return 0, errors.New("input range is invalid")
}
return randFloat64Range(min, max), nil
}

@ -0,0 +1,45 @@
package gofakeit
import "strconv"
// Faker::Beer.blg #=> "18.5°Blg"
// BeerName will return a random beer name
func BeerName() string {
return getRandValue([]string{"beer", "name"})
}
// BeerStyle will return a random beer style
func BeerStyle() string {
return getRandValue([]string{"beer", "style"})
}
// BeerHop will return a random beer hop
func BeerHop() string {
return getRandValue([]string{"beer", "hop"})
}
// BeerYeast will return a random beer yeast
func BeerYeast() string {
return getRandValue([]string{"beer", "yeast"})
}
// BeerMalt will return a random beer malt
func BeerMalt() string {
return getRandValue([]string{"beer", "malt"})
}
// BeerIbu will return a random beer ibu value between 10 and 100
func BeerIbu() string {
return strconv.Itoa(randIntRange(10, 100)) + " IBU"
}
// BeerAlcohol will return a random beer alcohol level between 2.0 and 10.0
func BeerAlcohol() string {
return strconv.FormatFloat(randFloat64Range(2.0, 10.0), 'f', 1, 64) + "%"
}
// BeerBlg will return a random beer blg between 5.0 and 20.0
func BeerBlg() string {
return strconv.FormatFloat(randFloat64Range(5.0, 20.0), 'f', 1, 64) + "°Blg"
}

@ -0,0 +1,10 @@
package gofakeit
// Bool will generate a random boolean value
func Bool() bool {
if randIntRange(0, 1) == 1 {
return true
}
return false
}

@ -0,0 +1,44 @@
package gofakeit
import "math/rand"
// Color will generate a random color string
func Color() string {
return getRandValue([]string{"color", "full"})
}
// SafeColor will generate a random safe color string
func SafeColor() string {
return getRandValue([]string{"color", "safe"})
}
// HexColor will generate a random hexadecimal color string
func HexColor() string {
color := make([]byte, 6)
hashQuestion := []byte("?#")
for i := 0; i < 6; i++ {
color[i] = hashQuestion[rand.Intn(2)]
}
return "#" + replaceWithLetters(replaceWithNumbers(string(color)))
// color := ""
// for i := 1; i <= 6; i++ {
// color += RandString([]string{"?", "#"})
// }
// // Replace # with number
// color = replaceWithNumbers(color)
// // Replace ? with letter
// for strings.Count(color, "?") > 0 {
// color = strings.Replace(color, "?", RandString(letters), 1)
// }
// return "#" + color
}
// RGBColor will generate a random int slice color
func RGBColor() []int {
return []int{randIntRange(0, 255), randIntRange(0, 255), randIntRange(0, 255)}
}

@ -0,0 +1,30 @@
package gofakeit
// Company will generate a random company name string
func Company() (company string) {
switch randInt := randIntRange(1, 3); randInt {
case 1:
company = LastName() + ", " + LastName() + " and " + LastName()
case 2:
company = LastName() + "-" + LastName()
case 3:
company = LastName() + " " + CompanySuffix()
}
return
}
// CompanySuffix will generate a random company suffix string
func CompanySuffix() string {
return getRandValue([]string{"company", "suffix"})
}
// BuzzWord will generate a random company buzz word string
func BuzzWord() string {
return getRandValue([]string{"company", "buzzwords"})
}
// BS will generate a random company bs string
func BS() string {
return getRandValue([]string{"company", "bs"})
}

@ -0,0 +1,40 @@
package gofakeit
import (
"strings"
)
// ContactInfo struct full of contact info
type ContactInfo struct {
Phone string
Email string
}
// Contact will generate a struct with information randomly populated contact information
func Contact() *ContactInfo {
return &ContactInfo{
Phone: Phone(),
Email: Email(),
}
}
// Phone will generate a random phone number string
func Phone() string {
return replaceWithNumbers("##########")
}
// PhoneFormatted will generate a random phone number string
func PhoneFormatted() string {
return replaceWithNumbers(getRandValue([]string{"contact", "phone"}))
}
// Email will generate a random email string
func Email() string {
var email string
email = getRandValue([]string{"person", "first"}) + getRandValue([]string{"person", "last"})
email += "@"
email += getRandValue([]string{"person", "last"}) + "." + getRandValue([]string{"internet", "domain_suffix"})
return strings.ToLower(email)
}

@ -0,0 +1,38 @@
package gofakeit
import (
"math"
"math/rand"
"github.com/brianvoe/gofakeit/data"
)
// CurrencyInfo is a struct of currency information
type CurrencyInfo struct {
Short string
Long string
}
// Currency will generate a struct with random currency information
func Currency() *CurrencyInfo {
index := rand.Intn(len(data.Data["currency"]["short"]))
return &CurrencyInfo{
Short: data.Data["currency"]["short"][index],
Long: data.Data["currency"]["long"][index],
}
}
// CurrencyShort will generate a random short currency value
func CurrencyShort() string {
return getRandValue([]string{"currency", "short"})
}
// CurrencyLong will generate a random long currency name
func CurrencyLong() string {
return getRandValue([]string{"currency", "long"})
}
// Price will take in a min and max value and return a formatted price
func Price(min, max float64) float64 {
return math.Floor(randFloat64Range(min, max)*100) / 100
}

@ -0,0 +1,15 @@
package data
// Address consists of address information
var Address = map[string][]string{
"number": {"#####", "####", "###"},
"street_prefix": {"North", "East", "West", "South", "New", "Lake", "Port"},
"street_name": {"Alley", "Avenue", "Branch", "Bridge", "Brook", "Brooks", "Burg", "Burgs", "Bypass", "Camp", "Canyon", "Cape", "Causeway", "Center", "Centers", "Circle", "Circles", "Cliff", "Cliffs", "Club", "Common", "Corner", "Corners", "Course", "Court", "Courts", "Cove", "Coves", "Creek", "Crescent", "Crest", "Crossing", "Crossroad", "Curve", "Dale", "Dam", "Divide", "Drive", "Drive", "Drives", "Estate", "Estates", "Expressway", "Extension", "Extensions", "Fall", "Falls", "Ferry", "Field", "Fields", "Flat", "Flats", "Ford", "Fords", "Forest", "Forge", "Forges", "Fork", "Forks", "Fort", "Freeway", "Garden", "Gardens", "Gateway", "Glen", "Glens", "Green", "Greens", "Grove", "Groves", "Harbor", "Harbors", "Haven", "Heights", "Highway", "Hill", "Hills", "Hollow", "Inlet", "Inlet", "Island", "Island", "Islands", "Islands", "Isle", "Isle", "Junction", "Junctions", "Key", "Keys", "Knoll", "Knolls", "Lake", "Lakes", "Land", "Landing", "Lane", "Light", "Lights", "Loaf", "Lock", "Locks", "Locks", "Lodge", "Lodge", "Loop", "Mall", "Manor", "Manors", "Meadow", "Meadows", "Mews", "Mill", "Mills", "Mission", "Mission", "Motorway", "Mount", "Mountain", "Mountain", "Mountains", "Mountains", "Neck", "Orchard", "Oval", "Overpass", "Park", "Parks", "Parkway", "Parkways", "Pass", "Passage", "Path", "Pike", "Pine", "Pines", "Place", "Plain", "Plains", "Plains", "Plaza", "Plaza", "Point", "Points", "Port", "Port", "Ports", "Ports", "Prairie", "Prairie", "Radial", "Ramp", "Ranch", "Rapid", "Rapids", "Rest", "Ridge", "Ridges", "River", "Road", "Road", "Roads", "Roads", "Route", "Row", "Rue", "Run", "Shoal", "Shoals", "Shore", "Shores", "Skyway", "Spring", "Springs", "Springs", "Spur", "Spurs", "Square", "Square", "Squares", "Squares", "Station", "Station", "Stravenue", "Stravenue", "Stream", "Stream", "Street", "Street", "Streets", "Summit", "Summit", "Terrace", "Throughway", "Trace", "Track", "Trafficway", "Trail", "Trail", "Tunnel", "Tunnel", "Turnpike", "Turnpike", "Underpass", "Union", "Unions", "Valley", "Valleys", "Via", "Viaduct", "View", "Views", "Village", "Village", "Villages", "Ville", "Vista", "Vista", "Walk", "Walks", "Wall", "Way", "Ways", "Well", "Wells"},
"street_suffix": {"town", "ton", "land", "ville", "berg", "burgh", "borough", "bury", "view", "port", "mouth", "stad", "furt", "chester", "mouth", "fort", "haven", "side", "shire"},
"city": {"{address.street_prefix} {name.first}{address.street_suffix}", "{address.street_prefix} {name.first}", "{name.first}{address.street_suffix}", "{name.last}{address.street_suffix}"},
"state": {"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"},
"state_abr": {"AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI", "VA", "WA", "WV", "WI", "WY", "AE", "AA", "AP"},
"zip": {"#####"},
"country": {"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", "Congo", "Cook Islands", "Costa Rica", "Cote Divoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Faroe Islands", "Falkland Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard Island and McDonald Islands", "Holy See (Vatican City State)", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea", "Korea", "Kuwait", "Kyrgyz Republic", "Lao Peoples Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands Antilles", "Netherlands", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Palestinian Territory", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia (Slovak Republic)", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard & Jan Mayen Islands", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States of America", "United States Minor Outlying Islands", "United States Virgin Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"},
"country_abr": {"AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CK", "CR", "CI", "HR", "CU", "CY", "CZ", "DK", "DJ", "DM", "DO", "TL", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "FX", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GN", "GW", "GY", "HT", "HM", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IL", "IT", "JM", "JP", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "AN", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "NO", "OM", "PK", "PW", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "KN", "LC", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SK", "SI", "SB", "SO", "ZA", "ES", "LK", "SH", "PM", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VA", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "YU", "ZR", "ZM", "ZW"},
}

@ -0,0 +1,10 @@
package data
// Beer consists of various beer information
var Beer = map[string][]string{
"name": {"Pliny The Elder", "Founders Kentucky Breakfast", "Trappistes Rochefort 10", "HopSlam Ale", "Stone Imperial Russian Stout", "St. Bernardus Abt 12", "Founders Breakfast Stout", "Weihenstephaner Hefeweissbier", "Péché Mortel", "Celebrator Doppelbock", "Duvel", "Dreadnaught IPA", "Nugget Nectar", "La Fin Du Monde", "Bourbon County Stout", "Old Rasputin Russian Imperial Stout", "Two Hearted Ale", "Ruination IPA", "Schneider Aventinus", "Double Bastard Ale", "90 Minute IPA", "Hop Rod Rye", "Trappistes Rochefort 8", "Chimay Grande Réserve", "Stone IPA", "Arrogant Bastard Ale", "Edmund Fitzgerald Porter", "Chocolate St", "Oak Aged Yeti Imperial Stout", "Ten FIDY", "Storm King Stout", "Shakespeare Oatmeal", "Alpha King Pale Ale", "Westmalle Trappist Tripel", "Samuel Smith’s Imperial IPA", "Yeti Imperial Stout", "Hennepin", "Samuel Smith’s Oatmeal Stout", "Brooklyn Black", "Oaked Arrogant Bastard Ale", "Sublimely Self-Righteous Ale", "Trois Pistoles", "Bell’s Expedition", "Sierra Nevada Celebration Ale", "Sierra Nevada Bigfoot Barleywine Style Ale", "Racer 5 India Pale Ale, Bear Republic Bre", "Orval Trappist Ale", "Hercules Double IPA", "Maharaj", "Maudite"},
"hop": {"Ahtanum", "Amarillo", "Bitter Gold", "Bravo", "Brewer’s Gold", "Bullion", "Cascade", "Cashmere", "Centennial", "Chelan", "Chinook", "Citra", "Cluster", "Columbia", "Columbus", "Comet", "Crystal", "Equinox", "Eroica", "Fuggle", "Galena", "Glacier", "Golding", "Hallertau", "Horizon", "Liberty", "Magnum", "Millennium", "Mosaic", "Mt. Hood", "Mt. Rainier", "Newport", "Northern Brewer", "Nugget", "Olympic", "Palisade", "Perle", "Saaz", "Santiam", "Simcoe", "Sorachi Ace", "Sterling", "Summit", "Tahoma", "Tettnang", "TriplePearl", "Ultra", "Vanguard", "Warrior", "Willamette", "Yakima Gol"},
"yeast": {"1007 - German Ale", "1010 - American Wheat", "1028 - London Ale", "1056 - American Ale", "1084 - Irish Ale", "1098 - British Ale", "1099 - Whitbread Ale", "1187 - Ringwood Ale", "1272 - American Ale II", "1275 - Thames Valley Ale", "1318 - London Ale III", "1332 - Northwest Ale", "1335 - British Ale II", "1450 - Dennys Favorite 50", "1469 - West Yorkshire Ale", "1728 - Scottish Ale", "1968 - London ESB Ale", "2565 - Kölsch", "1214 - Belgian Abbey", "1388 - Belgian Strong Ale", "1762 - Belgian Abbey II", "3056 - Bavarian Wheat Blend", "3068 - Weihenstephan Weizen", "3278 - Belgian Lambic Blend", "3333 - German Wheat", "3463 - Forbidden Fruit", "3522 - Belgian Ardennes", "3638 - Bavarian Wheat", "3711 - French Saison", "3724 - Belgian Saison", "3763 - Roeselare Ale Blend", "3787 - Trappist High Gravity", "3942 - Belgian Wheat", "3944 - Belgian Witbier", "2000 - Budvar Lager", "2001 - Urquell Lager", "2007 - Pilsen Lager", "2035 - American Lager", "2042 - Danish Lager", "2112 - California Lager", "2124 - Bohemian Lager", "2206 - Bavarian Lager", "2278 - Czech Pils", "2308 - Munich Lager", "2633 - Octoberfest Lager Blend", "5112 - Brettanomyces bruxellensis", "5335 - Lactobacillus", "5526 - Brettanomyces lambicus", "5733 - Pediococcus"},
"malt": {"Black malt", "Caramel", "Carapils", "Chocolate", "Munich", "Caramel", "Carapils", "Chocolate malt", "Munich", "Pale", "Roasted barley", "Rye malt", "Special roast", "Victory", "Vienna", "Wheat mal"},
"style": {"Light Lager", "Pilsner", "European Amber Lager", "Dark Lager", "Bock", "Light Hybrid Beer", "Amber Hybrid Beer", "English Pale Ale", "Scottish And Irish Ale", "Merican Ale", "English Brown Ale", "Porter", "Stout", "India Pale Ale", "German Wheat And Rye Beer", "Belgian And French Ale", "Sour Ale", "Belgian Strong Ale", "Strong Ale", "Fruit Beer", "Vegetable Beer", "Smoke-flavored", "Wood-aged Beer"},
}

@ -0,0 +1,7 @@
package data
// Colors consists of color information
var Colors = map[string][]string{
"safe": {"black", "maroon", "green", "navy", "olive", "purple", "teal", "lime", "blue", "silver", "gray", "yellow", "fuchsia", "aqua", "white"},
"full": {"AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "BlanchedAlmond", "Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue", "DarkCyan", "DarkGoldenRod", "DarkGray", "DarkGreen", "DarkKhaki", "DarkMagenta", "DarkOliveGreen", "Darkorange", "DarkOrchid", "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DimGrey", "DodgerBlue", "FireBrick", "FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "GoldenRod", "Gray", "Green", "GreenYellow", "HoneyDew", "HotPink", "IndianRed ", "Indigo ", "Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", "LightCoral", "LightCyan", "LightGoldenRodYellow", "LightGray", "LightGreen", "LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon", "MediumAquaMarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", "Orchid", "PaleGoldenRod", "PaleGreen", "PaleTurquoise", "PaleVioletRed", "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown", "Salmon", "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver", "SkyBlue", "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", "Teal", "Thistle", "Tomato", "Turquoise", "Violet", "Wheat", "White", "WhiteSmoke", "Yellow", "YellowGreen"},
}

@ -0,0 +1,9 @@
package data
// Company consists of company information
var Company = map[string][]string{
"name": {"{person.last} {company.suffix}", "{person.last}-{person.last}", "{person.last}, {person.last} and {person.last}"},
"suffix": {"Inc", "and Sons", "LLC", "Group"},
"buzzwords": {"Adaptive", "Advanced", "Ameliorated", "Assimilated", "Automated", "Balanced", "Business-focused", "Centralized", "Cloned", "Compatible", "Configurable", "Cross-group", "Cross-platform", "Customer-focused", "Customizable", "De-engineered", "Decentralized", "Devolved", "Digitized", "Distributed", "Diverse", "Down-sized", "Enhanced", "Enterprise-wide", "Ergonomic", "Exclusive", "Expanded", "Extended", "Face to face", "Focused", "Front-line", "Fully-configurable", "Function-based", "Fundamental", "Future-proofed", "Grass-roots", "Horizontal", "Implemented", "Innovative", "Integrated", "Intuitive", "Inverse", "Managed", "Mandatory", "Monitored", "Multi-channelled", "Multi-lateral", "Multi-layered", "Multi-tiered", "Networked", "Object-based", "Open-architected", "Open-source", "Operative", "Optimized", "Optional", "Organic", "Organized", "Persevering", "Persistent", "Phased", "Polarised", "Pre-emptive", "Proactive", "Profit-focused", "Profound", "Programmable", "Progressive", "Public-key", "Quality-focused", "Re-contextualized", "Re-engineered", "Reactive", "Realigned", "Reduced", "Reverse-engineered", "Right-sized", "Robust", "Seamless", "Secured", "Self-enabling", "Sharable", "Stand-alone", "Streamlined", "Switchable", "Synchronised", "Synergistic", "Synergized", "Team-oriented", "Total", "Triple-buffered", "Universal", "Up-sized", "Upgradable", "User-centric", "User-friendly", "Versatile", "Virtual", "Vision-oriented", "Visionary", "24 hour", "24/7", "3rd generation", "4th generation", "5th generation", "6th generation", "actuating", "analyzing", "asymmetric", "asynchronous", "attitude-oriented", "background", "bandwidth-monitored", "bi-directional", "bifurcated", "bottom-line", "clear-thinking", "client-driven", "client-server", "coherent", "cohesive", "composite", "content-based", "context-sensitive", "contextually-based", "dedicated", "demand-driven", "didactic", "directional", "discrete", "disintermediate", "dynamic", "eco-centric", "empowering", "encompassing", "even-keeled", "executive", "explicit", "exuding", "fault-tolerant", "foreground", "fresh-thinking", "full-range", "global", "grid-enabled", "heuristic", "high-level", "holistic", "homogeneous", "human-resource", "hybrid", "impactful", "incremental", "intangible", "interactive", "intermediate", "leading edge", "local", "logistical", "maximized", "methodical", "mission-critical", "mobile", "modular", "motivating", "multi-state", "multi-tasking", "multimedia", "national", "needs-based", "neutral", "next generation", "non-volatile", "object-oriented", "optimal", "optimizing", "radical", "real-time", "reciprocal", "regional", "responsive", "scalable", "secondary", "solution-oriented", "stable", "static", "system-worthy", "systematic", "systemic", "tangible", "tertiary", "transitional", "uniform", "upward-trending", "user-facing", "value-added", "web-enabled", "well-modulated", "zero administration", "zero defect", "zero tolerance", "Graphic Interface", "Graphical User Interface", "ability", "access", "adapter", "algorithm", "alliance", "analyzer", "application", "approach", "architecture", "archive", "array", "artificial intelligence", "attitude", "benchmark", "budgetary management", "capability", "capacity", "challenge", "circuit", "collaboration", "complexity", "concept", "conglomeration", "contingency", "core", "customer loyalty", "data-warehouse", "database", "definition", "emulation", "encoding", "encryption", "extranet", "firmware", "flexibility", "focus group", "forecast", "frame", "framework", "function", "functionalities", "groupware", "hardware", "help-desk", "hierarchy", "hub", "implementation", "info-mediaries", "infrastructure", "initiative", "installation", "instruction set", "interface", "internet solution", "intranet", "knowledge base", "knowledge user", "leverage", "local area network", "matrices", "matrix", "methodology", "middleware", "migration", "model", "moderator", "monitoring", "moratorium", "neural-net", "open architecture", "open system", "orchestration", "paradigm", "parallelism", "policy", "portal", "pricing structure", "process improvement", "product", "productivity", "project", "projection", "protocol", "secured line", "service-desk", "software", "solution", "standardization", "strategy", "structure", "success", "superstructure", "support", "synergy", "system engine", "task-force", "throughput", "time-frame", "toolset", "utilisation", "website", "workforce"},
"bs": {"aggregate", "architect", "benchmark", "brand", "cultivate", "deliver", "deploy", "disintermediate", "drive", "e-enable", "embrace", "empower", "enable", "engage", "engineer", "enhance", "envisioneer", "evolve", "expedite", "exploit", "extend", "facilitate", "generate", "grow", "harness", "implement", "incentivize", "incubate", "innovate", "integrate", "iterate", "leverage", "matrix", "maximize", "mesh", "monetize", "morph", "optimize", "orchestrate", "productize", "recontextualize", "redefine", "reintermediate", "reinvent", "repurpose", "revolutionize", "scale", "seize", "strategize", "streamline", "syndicate", "synergize", "synthesize", "target", "transform", "transition", "unleash", "utilize", "visualize", "whiteboard", "24/365", "24/7", "B2B", "B2C", "back-end", "best-of-breed", "bleeding-edge", "bricks-and-clicks", "clicks-and-mortar", "collaborative", "compelling", "cross-media", "cross-platform", "customized", "cutting-edge", "distributed", "dot-com", "dynamic", "e-business", "efficient", "end-to-end", "enterprise", "extensible", "frictionless", "front-end", "global", "granular", "holistic", "impactful", "innovative", "integrated", "interactive", "intuitive", "killer", "leading-edge", "magnetic", "mission-critical", "next-generation", "one-to-one", "open-source", "out-of-the-box", "plug-and-play", "proactive", "real-time", "revolutionary", "rich", "robust", "scalable", "seamless", "sexy", "sticky", "strategic", "synergistic", "transparent", "turn-key", "ubiquitous", "user-centric", "value-added", "vertical", "viral", "virtual", "visionary", "web-enabled", "wireless", "world-class", "ROI", "action-items", "applications", "architectures", "bandwidth", "channels", "communities", "content", "convergence", "deliverables", "e-business", "e-commerce", "e-markets", "e-services", "e-tailers", "experiences", "eyeballs", "functionalities", "infomediaries", "infrastructures", "initiatives", "interfaces", "markets", "methodologies", "metrics", "mindshare", "models", "networks", "niches", "paradigms", "partnerships", "platforms", "portals", "relationships", "schemas", "solutions", "supply-chains", "synergies", "systems", "technologies", "users", "vortals", "web services", "web-readiness"},
}

@ -0,0 +1,8 @@
package data
// Computer consists of computer information
var Computer = map[string][]string{
"linux_processor": {"i686", "x86_64"},
"mac_processor": {"Intel", "PPC", "U; Intel", "U; PPC"},
"windows_platform": {"Windows NT 6.2", "Windows NT 6.1", "Windows NT 6.0", "Windows NT 5.2", "Windows NT 5.1", "Windows NT 5.01", "Windows NT 5.0", "Windows NT 4.0", "Windows 98; Win 9x 4.90", "Windows 98", "Windows 95", "Windows CE"},
}

@ -0,0 +1,6 @@
package data
// Contact consists of contact information
var Contact = map[string][]string{
"phone": {"###-###-####", "(###)###-####", "1-###-###-####", "###.###.####"},
}

@ -0,0 +1,7 @@
package data
// Currency consists of currency information
var Currency = map[string][]string{
"short": {"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYR", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SPL", "SRD", "STD", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TVD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XCD", "XDR", "XOF", "XPF", "YER", "ZAR", "ZMW", "ZWD"},
"long": {"United Arab Emirates Dirham", "Afghanistan Afghani", "Albania Lek", "Armenia Dram", "Netherlands Antilles Guilder", "Angola Kwanza", "Argentina Peso", "Australia Dollar", "Aruba Guilder", "Azerbaijan New Manat", "Bosnia and Herzegovina Convertible Marka", "Barbados Dollar", "Bangladesh Taka", "Bulgaria Lev", "Bahrain Dinar", "Burundi Franc", "Bermuda Dollar", "Brunei Darussalam Dollar", "Bolivia Boliviano", "Brazil Real", "Bahamas Dollar", "Bhutan Ngultrum", "Botswana Pula", "Belarus Ruble", "Belize Dollar", "Canada Dollar", "Congo/Kinshasa Franc", "Switzerland Franc", "Chile Peso", "China Yuan Renminbi", "Colombia Peso", "Costa Rica Colon", "Cuba Convertible Peso", "Cuba Peso", "Cape Verde Escudo", "Czech Republic Koruna", "Djibouti Franc", "Denmark Krone", "Dominican Republic Peso", "Algeria Dinar", "Egypt Pound", "Eritrea Nakfa", "Ethiopia Birr", "Euro Member Countries", "Fiji Dollar", "Falkland Islands (Malvinas) Pound", "United Kingdom Pound", "Georgia Lari", "Guernsey Pound", "Ghana Cedi", "Gibraltar Pound", "Gambia Dalasi", "Guinea Franc", "Guatemala Quetzal", "Guyana Dollar", "Hong Kong Dollar", "Honduras Lempira", "Croatia Kuna", "Haiti Gourde", "Hungary Forint", "Indonesia Rupiah", "Israel Shekel", "Isle of Man Pound", "India Rupee", "Iraq Dinar", "Iran Rial", "Iceland Krona", "Jersey Pound", "Jamaica Dollar", "Jordan Dinar", "Japan Yen", "Kenya Shilling", "Kyrgyzstan Som", "Cambodia Riel", "Comoros Franc", "Korea (North) Won", "Korea (South) Won", "Kuwait Dinar", "Cayman Islands Dollar", "Kazakhstan Tenge", "Laos Kip", "Lebanon Pound", "Sri Lanka Rupee", "Liberia Dollar", "Lesotho Loti", "Lithuania Litas", "Libya Dinar", "Morocco Dirham", "Moldova Leu", "Madagascar Ariary", "Macedonia Denar", "Myanmar (Burma) Kyat", "Mongolia Tughrik", "Macau Pataca", "Mauritania Ouguiya", "Mauritius Rupee", "Maldives (Maldive Islands) Rufiyaa", "Malawi Kwacha", "Mexico Peso", "Malaysia Ringgit", "Mozambique Metical", "Namibia Dollar", "Nigeria Naira", "Nicaragua Cordoba", "Norway Krone", "Nepal Rupee", "New Zealand Dollar", "Oman Rial", "Panama Balboa", "Peru Nuevo Sol", "Papua New Guinea Kina", "Philippines Peso", "Pakistan Rupee", "Poland Zloty", "Paraguay Guarani", "Qatar Riyal", "Romania New Leu", "Serbia Dinar", "Russia Ruble", "Rwanda Franc", "Saudi Arabia Riyal", "Solomon Islands Dollar", "Seychelles Rupee", "Sudan Pound", "Sweden Krona", "Singapore Dollar", "Saint Helena Pound", "Sierra Leone Leone", "Somalia Shilling", "Seborga Luigino", "Suriname Dollar", "São Tomé and Príncipe Dobra", "El Salvador Colon", "Syria Pound", "Swaziland Lilangeni", "Thailand Baht", "Tajikistan Somoni", "Turkmenistan Manat", "Tunisia Dinar", "Tonga Pa'anga", "Turkey Lira", "Trinidad and Tobago Dollar", "Tuvalu Dollar", "Taiwan New Dollar", "Tanzania Shilling", "Ukraine Hryvnia", "Uganda Shilling", "United States Dollar", "Uruguay Peso", "Uzbekistan Som", "Venezuela Bolivar", "Viet Nam Dong", "Vanuatu Vatu", "Samoa Tala", "Communauté Financière Africaine (BEAC) CFA Franc BEAC", "East Caribbean Dollar", "International Monetary Fund (IMF) Special Drawing Rights", "Communauté Financière Africaine (BCEAO) Franc", "Comptoirs Français du Pacifique (CFP) Franc", "Yemen Rial", "South Africa Rand", "Zambia Kwacha", "Zimbabwe Dollar"},
}

@ -0,0 +1,28 @@
package data
// Data consists of the main set of fake information
var Data = map[string]map[string][]string{
"person": Person,
"contact": Contact,
"address": Address,
"company": Company,
"job": Job,
"lorem": Lorem,
"internet": Internet,
"file": Files,
"color": Colors,
"computer": Computer,
"payment": Payment,
"hipster": Hipster,
"beer": Beer,
"hacker": Hacker,
"currency": Currency,
"log_level": LogLevels,
"timezone": TimeZone,
"vehicle": Vehicle,
}
// IntData consists of the main set of fake information (integer only)
var IntData = map[string]map[string][]int{
"status_code": StatusCodes,
}

@ -0,0 +1,9 @@
package data
// TimeZone is an array of short and long timezones
var TimeZone = map[string][]string{
"offset": {"-12", "-11", "-10", "-8", "-7", "-7", "-8", "-7", "-6", "-6", "-6", "-5", "-5", "-6", "-5", "-4", "-4", "-4.5", "-4", "-3", "-4", "-4", "-4", "-2.5", "-3", "-3", "-3", "-3", "-3", "-3", "-2", "-1", "0", "-1", "1", "0", "0", "1", "1", "0", "2", "2", "2", "2", "1", "1", "3", "3", "2", "3", "3", "2", "3", "3", "3", "2", "3", "3", "3", "3", "3", "3", "4", "4.5", "4", "5", "4", "4", "4", "4.5", "5", "5", "5", "5.5", "5.5", "5.75", "6", "6", "6.5", "7", "7", "8", "8", "8", "8", "8", "8", "9", "9", "9", "9.5", "9.5", "10", "10", "10", "10", "10", "11", "11", "12", "12", "12", "12", "13", "13", "13"},
"abr": {"DST", "U", "HST", "AKDT", "PDT", "PDT", "PST", "UMST", "MDT", "MDT", "CAST", "CDT", "CDT", "CCST", "SPST", "EDT", "UEDT", "VST", "PYT", "ADT", "CBST", "SWST", "PSST", "NDT", "ESAST", "AST", "SEST", "GDT", "MST", "BST", "U", "MDT", "ADT", "CVST", "MDT", "UTC", "GMT", "BST", "GDT", "GST", "WEDT", "CEDT", "RDT", "CEDT", "WCAST", "NST", "GDT", "MEDT", "EST", "SDT", "EEDT", "SAST", "FDT", "TDT", "JDT", "LST", "JST", "AST", "KST", "AST", "EAST", "MSK", "SAMT", "IDT", "AST", "ADT", "MST", "GST", "CST", "AST", "WAST", "YEKT", "PKT", "IST", "SLST", "NST", "CAST", "BST", "MST", "SAST", "NCAST", "CST", "NAST", "MPST", "WAST", "TST", "UST", "NAEST", "JST", "KST", "CAST", "ACST", "EAST", "AEST", "WPST", "TST", "YST", "CPST", "VST", "NZST", "U", "FST", "MST", "KDT", "TST", "SST"},
"text": {"Dateline Standard Time", "UTC-11", "Hawaiian Standard Time", "Alaskan Standard Time", "Pacific Standard Time (Mexico)", "Pacific Daylight Time", "Pacific Standard Time", "US Mountain Standard Time", "Mountain Standard Time (Mexico)", "Mountain Standard Time", "Central America Standard Time", "Central Standard Time", "Central Standard Time (Mexico)", "Canada Central Standard Time", "SA Pacific Standard Time", "Eastern Standard Time", "US Eastern Standard Time", "Venezuela Standard Time", "Paraguay Standard Time", "Atlantic Standard Time", "Central Brazilian Standard Time", "SA Western Standard Time", "Pacific SA Standard Time", "Newfoundland Standard Time", "E. South America Standard Time", "Argentina Standard Time", "SA Eastern Standard Time", "Greenland Standard Time", "Montevideo Standard Time", "Bahia Standard Time", "UTC-02", "Mid-Atlantic Standard Time", "Azores Standard Time", "Cape Verde Standard Time", "Morocco Standard Time", "UTC", "Greenwich Mean Time", "British Summer Time", "GMT Standard Time", "Greenwich Standard Time", "W. Europe Standard Time", "Central Europe Standard Time", "Romance Standard Time", "Central European Standard Time", "W. Central Africa Standard Time", "Namibia Standard Time", "GTB Standard Time", "Middle East Standard Time", "Egypt Standard Time", "Syria Standard Time", "E. Europe Standard Time", "South Africa Standard Time", "FLE Standard Time", "Turkey Standard Time", "Israel Standard Time", "Libya Standard Time", "Jordan Standard Time", "Arabic Standard Time", "Kaliningrad Standard Time", "Arab Standard Time", "E. Africa Standard Time", "Moscow Standard Time", "Samara Time", "Iran Standard Time", "Arabian Standard Time", "Azerbaijan Standard Time", "Mauritius Standard Time", "Georgian Standard Time", "Caucasus Standard Time", "Afghanistan Standard Time", "West Asia Standard Time", "Yekaterinburg Time", "Pakistan Standard Time", "India Standard Time", "Sri Lanka Standard Time", "Nepal Standard Time", "Central Asia Standard Time", "Bangladesh Standard Time", "Myanmar Standard Time", "SE Asia Standard Time", "N. Central Asia Standard Time", "China Standard Time", "North Asia Standard Time", "Singapore Standard Time", "W. Australia Standard Time", "Taipei Standard Time", "Ulaanbaatar Standard Time", "North Asia East Standard Time", "Japan Standard Time", "Korea Standard Time", "Cen. Australia Standard Time", "AUS Central Standard Time", "E. Australia Standard Time", "AUS Eastern Standard Time", "West Pacific Standard Time", "Tasmania Standard Time", "Yakutsk Standard Time", "Central Pacific Standard Time", "Vladivostok Standard Time", "New Zealand Standard Time", "UTC+12", "Fiji Standard Time", "Magadan Standard Time", "Kamchatka Standard Time", "Tonga Standard Time", "Samoa Standard Time"},
"full": {"(UTC-12:00) International Date Line West", "(UTC-11:00) Coordinated Universal Time-11", "(UTC-10:00) Hawaii", "(UTC-09:00) Alaska", "(UTC-08:00) Baja California", "(UTC-07:00) Pacific Time (US & Canada)", "(UTC-08:00) Pacific Time (US & Canada)", "(UTC-07:00) Arizona", "(UTC-07:00) Chihuahua, La Paz, Mazatlan", "(UTC-07:00) Mountain Time (US & Canada)", "(UTC-06:00) Central America", "(UTC-06:00) Central Time (US & Canada)", "(UTC-06:00) Guadalajara, Mexico City, Monterrey", "(UTC-06:00) Saskatchewan", "(UTC-05:00) Bogota, Lima, Quito", "(UTC-05:00) Eastern Time (US & Canada)", "(UTC-05:00) Indiana (East)", "(UTC-04:30) Caracas", "(UTC-04:00) Asuncion", "(UTC-04:00) Atlantic Time (Canada)", "(UTC-04:00) Cuiaba", "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan", "(UTC-04:00) Santiago", "(UTC-03:30) Newfoundland", "(UTC-03:00) Brasilia", "(UTC-03:00) Buenos Aires", "(UTC-03:00) Cayenne, Fortaleza", "(UTC-03:00) Greenland", "(UTC-03:00) Montevideo", "(UTC-03:00) Salvador", "(UTC-02:00) Coordinated Universal Time-02", "(UTC-02:00) Mid-Atlantic - Old", "(UTC-01:00) Azores", "(UTC-01:00) Cape Verde Is.", "(UTC) Casablanca", "(UTC) Coordinated Universal Time", "(UTC) Edinburgh, London", "(UTC+01:00) Edinburgh, London", "(UTC) Dublin, Lisbon", "(UTC) Monrovia, Reykjavik", "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", "(UTC+01:00) West Central Africa", "(UTC+01:00) Windhoek", "(UTC+02:00) Athens, Bucharest", "(UTC+02:00) Beirut", "(UTC+02:00) Cairo", "(UTC+02:00) Damascus", "(UTC+02:00) E. Europe", "(UTC+02:00) Harare, Pretoria", "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", "(UTC+03:00) Istanbul", "(UTC+02:00) Jerusalem", "(UTC+02:00) Tripoli", "(UTC+03:00) Amman", "(UTC+03:00) Baghdad", "(UTC+03:00) Kaliningrad, Minsk", "(UTC+03:00) Kuwait, Riyadh", "(UTC+03:00) Nairobi", "(UTC+03:00) Moscow, St. Petersburg, Volgograd", "(UTC+04:00) Samara, Ulyanovsk, Saratov", "(UTC+03:30) Tehran", "(UTC+04:00) Abu Dhabi, Muscat", "(UTC+04:00) Baku", "(UTC+04:00) Port Louis", "(UTC+04:00) Tbilisi", "(UTC+04:00) Yerevan", "(UTC+04:30) Kabul", "(UTC+05:00) Ashgabat, Tashkent", "(UTC+05:00) Yekaterinburg", "(UTC+05:00) Islamabad, Karachi", "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", "(UTC+05:30) Sri Jayawardenepura", "(UTC+05:45) Kathmandu", "(UTC+06:00) Astana", "(UTC+06:00) Dhaka", "(UTC+06:30) Yangon (Rangoon)", "(UTC+07:00) Bangkok, Hanoi, Jakarta", "(UTC+07:00) Novosibirsk", "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", "(UTC+08:00) Krasnoyarsk", "(UTC+08:00) Kuala Lumpur, Singapore", "(UTC+08:00) Perth", "(UTC+08:00) Taipei", "(UTC+08:00) Ulaanbaatar", "(UTC+09:00) Irkutsk", "(UTC+09:00) Osaka, Sapporo, Tokyo", "(UTC+09:00) Seoul", "(UTC+09:30) Adelaide", "(UTC+09:30) Darwin", "(UTC+10:00) Brisbane", "(UTC+10:00) Canberra, Melbourne, Sydney", "(UTC+10:00) Guam, Port Moresby", "(UTC+10:00) Hobart", "(UTC+10:00) Yakutsk", "(UTC+11:00) Solomon Is., New Caledonia", "(UTC+11:00) Vladivostok", "(UTC+12:00) Auckland, Wellington", "(UTC+12:00) Coordinated Universal Time+12", "(UTC+12:00) Fiji", "(UTC+12:00) Magadan", "(UTC+12:00) Petropavlovsk-Kamchatsky - Old", "(UTC+13:00) Nuku'alofa", "(UTC+13:00) Samoa"},
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,20 @@
package data
// Hacker consists of random hacker phrases
var Hacker = map[string][]string{
"abbreviation": {"TCP", "HTTP", "SDD", "RAM", "GB", "CSS", "SSL", "AGP", "SQL", "FTP", "PCI", "AI", "ADP", "RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "SAS", "IB", "SCSI", "JSON", "XSS", "JBOD"},
"adjective": {"auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform", "redundant", "online", "haptic", "multi-byte", "bluetooth", "wireless", "1080p", "neural", "optical", "solid state", "mobile"},
"noun": {"driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card", "array", "interface", "system", "sensor", "firewall", "hard drive", "pixel", "alarm", "feed", "monitor", "application", "transmitter", "bus", "circuit", "capacitor", "matrix"},
"verb": {"back up", "bypass", "hack", "override", "compress", "copy", "navigate", "index", "connect", "generate", "quantify", "calculate", "synthesize", "input", "transmit", "program", "reboot", "parse"},
"ingverb": {"backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating", "indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing", "transmitting", "programming", "parsing"},
"phrase": {
"If we {hacker.verb} the {hacker.noun}, we can get to the {hacker.abbreviation} {hacker.noun} through the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!",
"We need to {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!",
"Try to {hacker.verb} the {hacker.abbreviation} {hacker.noun}, maybe it will {hacker.verb} the {hacker.adjective} {hacker.noun}!",
"You can't {hacker.verb} the {hacker.noun} without {hacker.ingverb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!",
"Use the {hacker.adjective} {hacker.abbreviation} {hacker.noun}, then you can {hacker.verb} the {hacker.adjective} {hacker.noun}!",
"The {hacker.abbreviation} {hacker.noun} is down, {hacker.verb} the {hacker.adjective} {hacker.noun} so we can {hacker.verb} the {hacker.abbreviation} {hacker.noun}!",
"{hacker.ingverb} the {hacker.noun} won't do anything, we need to {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}!",
"I'll {hacker.verb} the {hacker.adjective} {hacker.abbreviation} {hacker.noun}, that should {hacker.verb} the {hacker.abbreviation} {hacker.noun}!",
},
}

@ -0,0 +1,6 @@
package data
// Hipster consists of random hipster words
var Hipster = map[string][]string{
"word": {"Wes Anderson", "chicharrones", "narwhal", "food truck", "marfa", "aesthetic", "keytar", "art party", "sustainable", "forage", "mlkshk", "gentrify", "locavore", "swag", "hoodie", "microdosing", "VHS", "before they sold out", "pabst", "plaid", "Thundercats", "freegan", "scenester", "hella", "occupy", "truffaut", "raw denim", "beard", "post-ironic", "photo booth", "twee", "90's", "pitchfork", "cray", "cornhole", "kale chips", "pour-over", "yr", "five dollar toast", "kombucha", "you probably haven't heard of them", "mustache", "fixie", "try-hard", "franzen", "kitsch", "austin", "stumptown", "keffiyeh", "whatever", "tumblr", "DIY", "shoreditch", "biodiesel", "vegan", "pop-up", "banjo", "kogi", "cold-pressed", "letterpress", "chambray", "butcher", "synth", "trust fund", "hammock", "farm-to-table", "intelligentsia", "loko", "ugh", "offal", "poutine", "gastropub", "Godard", "jean shorts", "sriracha", "dreamcatcher", "leggings", "fashion axe", "church-key", "meggings", "tote bag", "disrupt", "readymade", "helvetica", "flannel", "meh", "roof", "hashtag", "knausgaard", "cronut", "schlitz", "green juice", "waistcoat", "normcore", "viral", "ethical", "actually", "fingerstache", "humblebrag", "deep v", "wayfarers", "tacos", "taxidermy", "selvage", "put a bird on it", "ramps", "portland", "retro", "kickstarter", "bushwick", "brunch", "distillery", "migas", "flexitarian", "XOXO", "small batch", "messenger bag", "heirloom", "tofu", "bicycle rights", "bespoke", "salvia", "wolf", "selfies", "echo", "park", "listicle", "craft beer", "chartreuse", "sartorial", "pinterest", "mumblecore", "kinfolk", "vinyl", "etsy", "umami", "8-bit", "polaroid", "banh mi", "crucifix", "bitters", "brooklyn", "PBR&B", "drinking", "vinegar", "squid", "tattooed", "skateboard", "vice", "authentic", "literally", "lomo", "celiac", "health", "goth", "artisan", "chillwave", "blue bottle", "pickled", "next level", "neutra", "organic", "Yuccie", "paleo", "blog", "single-origin coffee", "seitan", "street", "gluten-free", "mixtape", "venmo", "irony", "everyday", "carry", "slow-carb", "3 wolf moon", "direct trade", "lo-fi", "tousled", "tilde", "semiotics", "cred", "chia", "master", "cleanse", "ennui", "quinoa", "pug", "iPhone", "fanny pack", "cliche", "cardigan", "asymmetrical", "meditation", "YOLO", "typewriter", "pork belly", "shabby chic", "+1", "lumbersexual", "williamsburg"},
}

@ -0,0 +1,8 @@
package data
// Internet consists of various internet information
var Internet = map[string][]string{
"browser": {"firefox", "chrome", "internetExplorer", "opera", "safari"},
"domain_suffix": {"com", "biz", "info", "name", "net", "org", "io"},
"http_method": {"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"},
}

@ -0,0 +1,8 @@
package data
// Job consists of job data
var Job = map[string][]string{
"title": {"Administrator", "Agent", "Analyst", "Architect", "Assistant", "Associate", "Consultant", "Coordinator", "Designer", "Developer", "Director", "Engineer", "Executive", "Facilitator", "Liaison", "Manager", "Officer", "Orchestrator", "Planner", "Producer", "Representative", "Specialist", "Strategist", "Supervisor", "Technician"},
"descriptor": {"Central", "Chief", "Corporate", "Customer", "Direct", "District", "Dynamic", "Dynamic", "Forward", "Future", "Global", "Human", "Internal", "International", "Investor", "Lead", "Legacy", "National", "Principal", "Product", "Regional", "Senior"},
"level": {"Accountability", "Accounts", "Applications", "Assurance", "Brand", "Branding", "Communications", "Configuration", "Creative", "Data", "Directives", "Division", "Factors", "Functionality", "Group", "Identity", "Implementation", "Infrastructure", "Integration", "Interactions", "Intranet", "Marketing", "Markets", "Metrics", "Mobility", "Operations", "Optimization", "Paradigm", "Program", "Quality", "Research", "Response", "Security", "Solutions", "Tactics", "Usability", "Web"},
}

@ -0,0 +1,8 @@
package data
// LogLevels consists of log levels for several types
var LogLevels = map[string][]string{
"general": {"error", "warning", "info", "fatal", "trace", "debug"},
"syslog": {"emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"},
"apache": {"emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace1-8"},
}

@ -0,0 +1,6 @@
package data
// Lorem consists of lorem ipsum information
var Lorem = map[string][]string{
"word": {"alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat"},
}

@ -0,0 +1,20 @@
package data
// Payment contains payment information
var Payment = map[string][]string{
"card_type": {"Visa", "MasterCard", "American Express", "Discover"},
"number": {
// Visa
"4###############",
"4###############",
// Mastercard
"222100##########",
"272099##########",
// American Express
"34#############",
"37#############",
// Discover
"65##############",
"65##############",
},
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,7 @@
package data
// StatusCodes consists of commonly used HTTP status codes
var StatusCodes = map[string][]int{
"simple": {200, 301, 302, 400, 404, 500},
"general": {100, 200, 201, 203, 204, 205, 301, 302, 304, 400, 401, 403, 404, 405, 406, 416, 500, 501, 502, 503, 504},
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,77 @@
package gofakeit
import (
"strconv"
"time"
)
// Date will generate a random time.Time struct
func Date() time.Time {
return time.Date(Year(), time.Month(Number(0, 12)), Day(), Hour(), Minute(), Second(), NanoSecond(), time.UTC)
}
// DateRange will generate a random time.Time struct between a start and end date
func DateRange(start, end time.Time) time.Time {
return time.Unix(0, int64(Number(int(start.UnixNano()), int(end.UnixNano())))).UTC()
}
// Month will generate a random month string
func Month() string {
return time.Month(Number(1, 12)).String()
}
// Day will generate a random day between 1 - 31
func Day() int {
return Number(1, 31)
}
// WeekDay will generate a random weekday string (Monday-Sunday)
func WeekDay() string {
return time.Weekday(Number(0, 6)).String()
}
// Year will generate a random year between 1900 - current year
func Year() int {
return Number(1900, time.Now().Year())
}
// Hour will generate a random hour - in military time
func Hour() int {
return Number(0, 23)
}
// Minute will generate a random minute
func Minute() int {
return Number(0, 59)
}
// Second will generate a random second
func Second() int {
return Number(0, 59)
}
// NanoSecond will generate a random nano second
func NanoSecond() int {
return Number(0, 999999999)
}
// TimeZone will select a random timezone string
func TimeZone() string {
return getRandValue([]string{"timezone", "text"})
}
// TimeZoneFull will select a random full timezone string
func TimeZoneFull() string {
return getRandValue([]string{"timezone", "full"})
}
// TimeZoneAbv will select a random timezone abbreviation string
func TimeZoneAbv() string {
return getRandValue([]string{"timezone", "abr"})
}
// TimeZoneOffset will select a random timezone offset
func TimeZoneOffset() float32 {
value, _ := strconv.ParseFloat(getRandValue([]string{"timezone", "offset"}), 32)
return float32(value)
}

@ -0,0 +1,10 @@
/*
Package gofakeit is a random data generator written in go
Every function has an example and a benchmark
See the full list here https://godoc.org/github.com/brianvoe/gofakeit
80+ Functions!!!
*/
package gofakeit

@ -0,0 +1,15 @@
package gofakeit
import (
"math/rand"
"time"
)
// Seed random. Setting seed to 0 will use time.Now().UnixNano()
func Seed(seed int64) {
if seed == 0 {
rand.Seed(time.Now().UTC().UnixNano())
} else {
rand.Seed(seed)
}
}

@ -0,0 +1,11 @@
package gofakeit
// MimeType will generate a random mime file type
func MimeType() string {
return getRandValue([]string{"file", "mime_type"})
}
// Extension will generate a random file extension
func Extension() string {
return getRandValue([]string{"file", "extension"})
}

@ -0,0 +1,41 @@
package gofakeit
import (
"strings"
)
// Generate fake information from given string. String should contain {category.subcategory}
//
// Ex: {person.first} - random firstname
//
// Ex: {person.first}###{person.last}@{person.last}.{internet.domain_suffix} - billy834smith@smith.com
//
// Ex: ### - 481 - random numbers
//
// Ex: ??? - fda - random letters
//
// For a complete list possible categories use the Categories() function.
func Generate(dataVal string) string {
// Identify items between brackets: {person.first}
for strings.Count(dataVal, "{") > 0 && strings.Count(dataVal, "}") > 0 {
catValue := ""
startIndex := strings.Index(dataVal, "{")
endIndex := strings.Index(dataVal, "}")
replace := dataVal[(startIndex + 1):endIndex]
categories := strings.Split(replace, ".")
if len(categories) >= 2 && dataCheck([]string{categories[0], categories[1]}) {
catValue = getRandValue([]string{categories[0], categories[1]})
}
dataVal = strings.Replace(dataVal, "{"+replace+"}", catValue, 1)
}
// Replace # with numbers
dataVal = replaceWithNumbers(dataVal)
// Replace ? with letters
dataVal = replaceWithLetters(dataVal)
return dataVal
}

@ -0,0 +1,35 @@
package gofakeit
import "strings"
// HackerPhrase will return a random hacker sentence
func HackerPhrase() string {
words := strings.Split(Generate(getRandValue([]string{"hacker", "phrase"})), " ")
words[0] = strings.Title(words[0])
return strings.Join(words, " ")
}
// HackerAbbreviation will return a random hacker abbreviation
func HackerAbbreviation() string {
return getRandValue([]string{"hacker", "abbreviation"})
}
// HackerAdjective will return a random hacker adjective
func HackerAdjective() string {
return getRandValue([]string{"hacker", "adjective"})
}
// HackerNoun will return a random hacker noun
func HackerNoun() string {
return getRandValue([]string{"hacker", "noun"})
}
// HackerVerb will return a random hacker verb
func HackerVerb() string {
return getRandValue([]string{"hacker", "verb"})
}
// HackerIngverb will return a random hacker ingverb
func HackerIngverb() string {
return getRandValue([]string{"hacker", "ingverb"})
}

@ -0,0 +1,20 @@
package gofakeit
// HipsterWord will return a single hipster word
func HipsterWord() string {
return getRandValue([]string{"hipster", "word"})
}
// HipsterSentence will generate a random sentence
func HipsterSentence(wordCount int) string {
return sentence(wordCount, HipsterWord)
}
// HipsterParagraph will generate a random paragraphGenerator
// Set Paragraph Count
// Set Sentence Count
// Set Word Count
// Set Paragraph Separator
func HipsterParagraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string {
return paragraphGenerator(paragrapOptions{paragraphCount, sentenceCount, wordCount, separator}, HipsterSentence)
}

@ -0,0 +1,8 @@
package gofakeit
import "strconv"
// ImageURL will generate a random Image Based Upon Height And Width. https://picsum.photos/
func ImageURL(width int, height int) string {
return "https://picsum.photos/" + strconv.Itoa(width) + "/" + strconv.Itoa(height)
}

@ -0,0 +1,55 @@
package gofakeit
import (
"fmt"
"math/rand"
"strings"
)
// DomainName will generate a random url domain name
func DomainName() string {
return strings.ToLower(JobDescriptor()+BS()) + "." + DomainSuffix()
}
// DomainSuffix will generate a random domain suffix
func DomainSuffix() string {
return getRandValue([]string{"internet", "domain_suffix"})
}
// URL will generate a random url string
func URL() string {
url := "http" + RandString([]string{"s", ""}) + "://www."
url += DomainName()
// Slugs
num := Number(1, 4)
slug := make([]string, num)
for i := 0; i < num; i++ {
slug[i] = BS()
}
url += "/" + strings.ToLower(strings.Join(slug, "/"))
return url
}
// HTTPMethod will generate a random http method
func HTTPMethod() string {
return getRandValue([]string{"internet", "http_method"})
}
// IPv4Address will generate a random version 4 ip address
func IPv4Address() string {
num := func() int { return 2 + rand.Intn(254) }
return fmt.Sprintf("%d.%d.%d.%d", num(), num(), num(), num())
}
// IPv6Address will generate a random version 6 ip address
func IPv6Address() string {
num := 65536
return fmt.Sprintf("2001:cafe:%x:%x:%x:%x:%x:%x", rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num), rand.Intn(num))
}
// Username will genrate a random username based upon picking a random lastname and random numbers at the end
func Username() string {
return getRandValue([]string{"person", "last"}) + replaceWithNumbers("####")
}

@ -0,0 +1,34 @@
package gofakeit
// JobInfo is a struct of job information
type JobInfo struct {
Company string
Title string
Descriptor string
Level string
}
// Job will generate a struct with random job information
func Job() *JobInfo {
return &JobInfo{
Company: Company(),
Title: JobTitle(),
Descriptor: JobDescriptor(),
Level: JobLevel(),
}
}
// JobTitle will generate a random job title string
func JobTitle() string {
return getRandValue([]string{"job", "title"})
}
// JobDescriptor will generate a random job descriptor string
func JobDescriptor() string {
return getRandValue([]string{"job", "descriptor"})
}
// JobLevel will generate a random job level string
func JobLevel() string {
return getRandValue([]string{"job", "level"})
}

@ -0,0 +1,15 @@
package gofakeit
import (
"github.com/brianvoe/gofakeit/data"
)
// LogLevel will generate a random log level
// See data/LogLevels for list of available levels
func LogLevel(logType string) string {
if _, ok := data.LogLevels[logType]; ok {
return getRandValue([]string{"log_level", logType})
}
return getRandValue([]string{"log_level", "general"})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -0,0 +1,132 @@
package gofakeit
import (
"math/rand"
"github.com/brianvoe/gofakeit/data"
)
const hashtag = '#'
const questionmark = '?'
// Check if in lib
func dataCheck(dataVal []string) bool {
var checkOk bool
if len(dataVal) == 2 {
_, checkOk = data.Data[dataVal[0]]
if checkOk {
_, checkOk = data.Data[dataVal[0]][dataVal[1]]
}
}
return checkOk
}
// Check if in lib
func intDataCheck(dataVal []string) bool {
if len(dataVal) != 2 {
return false
}
_, checkOk := data.IntData[dataVal[0]]
if checkOk {
_, checkOk = data.IntData[dataVal[0]][dataVal[1]]
}
return checkOk
}
// Get Random Value
func getRandValue(dataVal []string) string {
if !dataCheck(dataVal) {
return ""
}
return data.Data[dataVal[0]][dataVal[1]][rand.Intn(len(data.Data[dataVal[0]][dataVal[1]]))]
}
// Get Random Integer Value
func getRandIntValue(dataVal []string) int {
if !intDataCheck(dataVal) {
return 0
}
return data.IntData[dataVal[0]][dataVal[1]][rand.Intn(len(data.IntData[dataVal[0]][dataVal[1]]))]
}
// Replace # with numbers
func replaceWithNumbers(str string) string {
if str == "" {
return str
}
bytestr := []byte(str)
for i := 0; i < len(bytestr); i++ {
if bytestr[i] == hashtag {
bytestr[i] = byte(randDigit())
}
}
if bytestr[0] == '0' {
bytestr[0] = byte(rand.Intn(8)+1) + '0'
}
return string(bytestr)
}
// Replace ? with ASCII lowercase letters
func replaceWithLetters(str string) string {
if str == "" {
return str
}
bytestr := []byte(str)
for i := 0; i < len(bytestr); i++ {
if bytestr[i] == questionmark {
bytestr[i] = byte(randLetter())
}
}
return string(bytestr)
}
// Generate random lowercase ASCII letter
func randLetter() rune {
return rune(byte(rand.Intn(26)) + 'a')
}
// Generate random ASCII digit
func randDigit() rune {
return rune(byte(rand.Intn(10)) + '0')
}
// Generate random integer between min and max
func randIntRange(min, max int) int {
if min == max {
return min
}
return rand.Intn((max+1)-min) + min
}
func randFloat32Range(min, max float32) float32 {
if min == max {
return min
}
return rand.Float32()*(max-min) + min
}
func randFloat64Range(min, max float64) float64 {
if min == max {
return min
}
return rand.Float64()*(max-min) + min
}
// Categories will return a map string array of available data categories and sub categories
func Categories() map[string][]string {
types := make(map[string][]string)
for category, subCategoriesMap := range data.Data {
subCategories := make([]string, 0)
for subType := range subCategoriesMap {
subCategories = append(subCategories, subType)
}
types[category] = subCategories
}
return types
}

@ -0,0 +1,26 @@
package gofakeit
// Name will generate a random First and Last Name
func Name() string {
return getRandValue([]string{"person", "first"}) + " " + getRandValue([]string{"person", "last"})
}
// FirstName will generate a random first name
func FirstName() string {
return getRandValue([]string{"person", "first"})
}
// LastName will generate a random last name
func LastName() string {
return getRandValue([]string{"person", "last"})
}
// NamePrefix will generate a random name prefix
func NamePrefix() string {
return getRandValue([]string{"person", "prefix"})
}
// NameSuffix will generate a random name suffix
func NameSuffix() string {
return getRandValue([]string{"person", "suffix"})
}

@ -0,0 +1,84 @@
package gofakeit
import (
"math"
"math/rand"
)
// Number will generate a random number between given min And max
func Number(min int, max int) int {
return randIntRange(min, max)
}
// Uint8 will generate a random uint8 value
func Uint8() uint8 {
return uint8(randIntRange(0, math.MaxUint8))
}
// Uint16 will generate a random uint16 value
func Uint16() uint16 {
return uint16(randIntRange(0, math.MaxUint16))
}
// Uint32 will generate a random uint32 value
func Uint32() uint32 {
return uint32(randIntRange(0, math.MaxInt32))
}
// Uint64 will generate a random uint64 value
func Uint64() uint64 {
return uint64(rand.Int63n(math.MaxInt64))
}
// Int8 will generate a random Int8 value
func Int8() int8 {
return int8(randIntRange(math.MinInt8, math.MaxInt8))
}
// Int16 will generate a random int16 value
func Int16() int16 {
return int16(randIntRange(math.MinInt16, math.MaxInt16))
}
// Int32 will generate a random int32 value
func Int32() int32 {
return int32(randIntRange(math.MinInt32, math.MaxInt32))
}
// Int64 will generate a random int64 value
func Int64() int64 {
return rand.Int63n(math.MaxInt64) + math.MinInt64
}
// Float32 will generate a random float32 value
func Float32() float32 {
return randFloat32Range(math.SmallestNonzeroFloat32, math.MaxFloat32)
}
// Float32Range will generate a random float32 value between min and max
func Float32Range(min, max float32) float32 {
return randFloat32Range(min, max)
}
// Float64 will generate a random float64 value
func Float64() float64 {
return randFloat64Range(math.SmallestNonzeroFloat64, math.MaxFloat64)
}
// Float64Range will generate a random float64 value between min and max
func Float64Range(min, max float64) float64 {
return randFloat64Range(min, max)
}
// Numerify will replace # with random numerical values
func Numerify(str string) string {
return replaceWithNumbers(str)
}
// ShuffleInts will randomize a slice of ints
func ShuffleInts(a []int) {
for i := range a {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
}

@ -0,0 +1,68 @@
package gofakeit
import (
"math/rand"
)
const lowerStr = "abcdefghijklmnopqrstuvwxyz"
const upperStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const numericStr = "0123456789"
const specialStr = "!@#$%&*+-=?"
const spaceStr = " "
// Password will generate a random password
// Minimum number length of 5 if less than
func Password(lower bool, upper bool, numeric bool, special bool, space bool, num int) string {
// Make sure the num minimun is at least 5
if num < 5 {
num = 5
}
i := 0
b := make([]byte, num)
var passString string
if lower {
passString += lowerStr
b[i] = lowerStr[rand.Int63()%int64(len(lowerStr))]
i++
}
if upper {
passString += upperStr
b[i] = upperStr[rand.Int63()%int64(len(upperStr))]
i++
}
if numeric {
passString += numericStr
b[i] = numericStr[rand.Int63()%int64(len(numericStr))]
i++
}
if special {
passString += specialStr
b[i] = specialStr[rand.Int63()%int64(len(specialStr))]
i++
}
if space {
passString += spaceStr
b[i] = spaceStr[rand.Int63()%int64(len(spaceStr))]
i++
}
// Set default if empty
if passString == "" {
passString = lowerStr + numericStr
}
// Loop through and add it up
for i <= num-1 {
b[i] = passString[rand.Int63()%int64(len(passString))]
i++
}
// Shuffle bytes
for i := range b {
j := rand.Intn(i + 1)
b[i], b[j] = b[j], b[i]
}
return string(b)
}

@ -0,0 +1,81 @@
package gofakeit
import "strconv"
import "time"
var currentYear = time.Now().Year() - 2000
// CreditCardInfo is a struct containing credit variables
type CreditCardInfo struct {
Type string
Number int
Exp string
Cvv string
}
// CreditCard will generate a struct full of credit card information
func CreditCard() *CreditCardInfo {
return &CreditCardInfo{
Type: CreditCardType(),
Number: CreditCardNumber(),
Exp: CreditCardExp(),
Cvv: CreditCardCvv(),
}
}
// CreditCardType will generate a random credit card type string
func CreditCardType() string {
return getRandValue([]string{"payment", "card_type"})
}
// CreditCardNumber will generate a random credit card number int
func CreditCardNumber() int {
integer, _ := strconv.Atoi(replaceWithNumbers(getRandValue([]string{"payment", "number"})))
return integer
}
// CreditCardNumberLuhn will generate a random credit card number int that passes luhn test
func CreditCardNumberLuhn() int {
cc := ""
for i := 0; i < 100000; i++ {
cc = replaceWithNumbers(getRandValue([]string{"payment", "number"}))
if luhn(cc) {
break
}
}
integer, _ := strconv.Atoi(cc)
return integer
}
// CreditCardExp will generate a random credit card expiration date string
// Exp date will always be a future date
func CreditCardExp() string {
month := strconv.Itoa(randIntRange(1, 12))
if len(month) == 1 {
month = "0" + month
}
return month + "/" + strconv.Itoa(randIntRange(currentYear+1, currentYear+10))
}
// CreditCardCvv will generate a random CVV number - Its a string because you could have 017 as an exp date
func CreditCardCvv() string {
return Numerify("###")
}
// luhn check is used for checking if credit card is valid
func luhn(s string) bool {
var t = [...]int{0, 2, 4, 6, 8, 1, 3, 5, 7, 9}
odd := len(s) & 1
var sum int
for i, c := range s {
if c < '0' || c > '9' {
return false
}
if i&1 == odd {
sum += t[c-'0']
} else {
sum += int(c - '0')
}
}
return sum%10 == 0
}

@ -0,0 +1,45 @@
package gofakeit
import "strconv"
// SSN will generate a random Social Security Number
func SSN() string {
return strconv.Itoa(randIntRange(100000000, 999999999))
}
// Gender will generate a random gender string
func Gender() string {
if Bool() == true {
return "male"
}
return "female"
}
// PersonInfo is a struct of person information
type PersonInfo struct {
FirstName string
LastName string
Gender string
SSN string
Image string
Job *JobInfo
Address *AddressInfo
Contact *ContactInfo
CreditCard *CreditCardInfo
}
// Person will generate a struct with person information
func Person() *PersonInfo {
return &PersonInfo{
FirstName: FirstName(),
LastName: LastName(),
Gender: Gender(),
SSN: SSN(),
Image: ImageURL(300, 300) + "/people",
Job: Job(),
Address: Address(),
Contact: Contact(),
CreditCard: CreditCard(),
}
}

@ -0,0 +1,11 @@
package gofakeit
// SimpleStatusCode will generate a random simple status code
func SimpleStatusCode() int {
return getRandIntValue([]string{"status_code", "simple"})
}
// StatusCode will generate a random status code
func StatusCode() int {
return getRandIntValue([]string{"status_code", "general"})
}

@ -0,0 +1,48 @@
package gofakeit
import (
"math/rand"
)
// Letter will generate a single random lower case ASCII letter
func Letter() string {
return string(randLetter())
}
// Digit will generate a single ASCII digit
func Digit() string {
return string(randDigit())
}
// Lexify will replace ? will random generated letters
func Lexify(str string) string {
return replaceWithLetters(str)
}
// ShuffleStrings will randomize a slice of strings
func ShuffleStrings(a []string) {
swap := func(i, j int) {
a[i], a[j] = a[j], a[i]
}
//to avoid upgrading to 1.10 I copied the algorithm
n := len(a)
if n <= 1 {
return
}
//if size is > int32 probably it will never finish, or ran out of entropy
i := n - 1
for ; i > 0; i-- {
j := int(rand.Int31n(int32(i + 1)))
swap(i, j)
}
}
// RandString will take in a slice of string and return a randomly selected value
func RandString(a []string) string {
size := len(a)
if size == 0 {
return ""
}
return a[rand.Intn(size)]
}

@ -0,0 +1,87 @@
package gofakeit
import (
"reflect"
)
// Struct fills in exported elements of a struct with random data
// based on the value of `fake` tag of exported elements.
// Use `fake:"skip"` to explicitly skip an element.
// All built-in types are supported, with templating support
// for string types.
func Struct(v interface{}) {
r(reflect.TypeOf(v), reflect.ValueOf(v), "")
}
func r(t reflect.Type, v reflect.Value, template string) {
switch t.Kind() {
case reflect.Ptr:
rPointer(t, v, template)
case reflect.Struct:
rStruct(t, v)
case reflect.String:
rString(template, v)
case reflect.Uint8:
v.SetUint(uint64(Uint8()))
case reflect.Uint16:
v.SetUint(uint64(Uint16()))
case reflect.Uint32:
v.SetUint(uint64(Uint32()))
case reflect.Uint64:
//capped at [0, math.MaxInt64)
v.SetUint(uint64(Uint64()))
case reflect.Int:
v.SetInt(int64(Int64()))
case reflect.Int8:
v.SetInt(int64(Int8()))
case reflect.Int16:
v.SetInt(int64(Int16()))
case reflect.Int32:
v.SetInt(int64(Int32()))
case reflect.Int64:
v.SetInt(int64(Int64()))
case reflect.Float64:
v.SetFloat(Float64())
case reflect.Float32:
v.SetFloat(float64(Float32()))
case reflect.Bool:
v.SetBool(Bool())
}
}
func rString(template string, v reflect.Value) {
if template != "" {
r := Generate(template)
v.SetString(r)
} else {
v.SetString(Generate("???????????????????"))
// we don't have a String(len int) string function!!
}
}
func rStruct(t reflect.Type, v reflect.Value) {
n := t.NumField()
for i := 0; i < n; i++ {
elementT := t.Field(i)
elementV := v.Field(i)
fake := true
t, ok := elementT.Tag.Lookup("fake")
if ok && t == "skip" {
fake = false
}
if fake && elementV.CanSet() {
r(elementT.Type, elementV, t)
}
}
}
func rPointer(t reflect.Type, v reflect.Value, template string) {
elemT := t.Elem()
if v.IsNil() {
nv := reflect.New(elemT)
r(elemT, nv.Elem(), template)
v.Set(nv)
} else {
r(elemT, v.Elem(), template)
}
}

@ -0,0 +1,34 @@
package gofakeit
import (
"encoding/hex"
"math/rand"
)
// UUID (version 4) will generate a random unique identifier based upon random nunbers
// Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
func UUID() string {
version := byte(4)
uuid := make([]byte, 16)
rand.Read(uuid)
// Set version
uuid[6] = (uuid[6] & 0x0f) | (version << 4)
// Set variant
uuid[8] = (uuid[8] & 0xbf) | 0x80
buf := make([]byte, 36)
var dash byte = '-'
hex.Encode(buf[0:8], uuid[0:4])
buf[8] = dash
hex.Encode(buf[9:13], uuid[4:6])
buf[13] = dash
hex.Encode(buf[14:18], uuid[6:8])
buf[18] = dash
hex.Encode(buf[19:23], uuid[8:10])
buf[23] = dash
hex.Encode(buf[24:], uuid[10:])
return string(buf)
}

@ -0,0 +1,92 @@
package gofakeit
import "strconv"
// UserAgent will generate a random broswer user agent
func UserAgent() string {
randNum := randIntRange(0, 4)
switch randNum {
case 0:
return ChromeUserAgent()
case 1:
return FirefoxUserAgent()
case 2:
return SafariUserAgent()
case 3:
return OperaUserAgent()
default:
return ChromeUserAgent()
}
}
// ChromeUserAgent will generate a random chrome browser user agent string
func ChromeUserAgent() string {
randNum1 := strconv.Itoa(randIntRange(531, 536)) + strconv.Itoa(randIntRange(0, 2))
randNum2 := strconv.Itoa(randIntRange(36, 40))
randNum3 := strconv.Itoa(randIntRange(800, 899))
return "Mozilla/5.0 " + "(" + randomPlatform() + ") AppleWebKit/" + randNum1 + " (KHTML, like Gecko) Chrome/" + randNum2 + ".0." + randNum3 + ".0 Mobile Safari/" + randNum1
}
// FirefoxUserAgent will generate a random firefox broswer user agent string
func FirefoxUserAgent() string {
ver := "Gecko/" + Date().Format("2006-02-01") + " Firefox/" + strconv.Itoa(randIntRange(35, 37)) + ".0"
platforms := []string{
"(" + windowsPlatformToken() + "; " + "en-US" + "; rv:1.9." + strconv.Itoa(randIntRange(0, 3)) + ".20) " + ver,
"(" + linuxPlatformToken() + "; rv:" + strconv.Itoa(randIntRange(5, 8)) + ".0) " + ver,
"(" + macPlatformToken() + " rv:" + strconv.Itoa(randIntRange(2, 7)) + ".0) " + ver,
}
return "Mozilla/5.0 " + RandString(platforms)
}
// SafariUserAgent will generate a random safari browser user agent string
func SafariUserAgent() string {
randNum := strconv.Itoa(randIntRange(531, 536)) + "." + strconv.Itoa(randIntRange(1, 51)) + "." + strconv.Itoa(randIntRange(1, 8))
ver := strconv.Itoa(randIntRange(4, 6)) + "." + strconv.Itoa(randIntRange(0, 2))
mobileDevices := []string{
"iPhone; CPU iPhone OS",
"iPad; CPU OS",
}
platforms := []string{
"(Windows; U; " + windowsPlatformToken() + ") AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + ver + " Safari/" + randNum,
"(" + macPlatformToken() + " rv:" + strconv.Itoa(randIntRange(4, 7)) + ".0; en-US) AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + ver + " Safari/" + randNum,
"(" + RandString(mobileDevices) + " " + strconv.Itoa(randIntRange(7, 9)) + "_" + strconv.Itoa(randIntRange(0, 3)) + "_" + strconv.Itoa(randIntRange(1, 3)) + " like Mac OS X; " + "en-US" + ") AppleWebKit/" + randNum + " (KHTML, like Gecko) Version/" + strconv.Itoa(randIntRange(3, 5)) + ".0.5 Mobile/8B" + strconv.Itoa(randIntRange(111, 120)) + " Safari/6" + randNum,
}
return "Mozilla/5.0 " + RandString(platforms)
}
// OperaUserAgent will generate a random opera browser user agent string
func OperaUserAgent() string {
platform := "(" + randomPlatform() + "; en-US) Presto/2." + strconv.Itoa(randIntRange(8, 13)) + "." + strconv.Itoa(randIntRange(160, 355)) + " Version/" + strconv.Itoa(randIntRange(10, 13)) + ".00"
return "Opera/" + strconv.Itoa(randIntRange(8, 10)) + "." + strconv.Itoa(randIntRange(10, 99)) + " " + platform
}
// linuxPlatformToken will generate a random linux platform
func linuxPlatformToken() string {
return "X11; Linux " + getRandValue([]string{"computer", "linux_processor"})
}
// macPlatformToken will generate a random mac platform
func macPlatformToken() string {
return "Macintosh; " + getRandValue([]string{"computer", "mac_processor"}) + " Mac OS X 10_" + strconv.Itoa(randIntRange(5, 9)) + "_" + strconv.Itoa(randIntRange(0, 10))
}
// windowsPlatformToken will generate a random windows platform
func windowsPlatformToken() string {
return getRandValue([]string{"computer", "windows_platform"})
}
// randomPlatform will generate a random platform
func randomPlatform() string {
platforms := []string{
linuxPlatformToken(),
macPlatformToken(),
windowsPlatformToken(),
}
return RandString(platforms)
}

@ -0,0 +1,55 @@
package gofakeit
// VehicleInfo is a struct dataset of all vehicle information
type VehicleInfo struct {
// Vehicle type
VehicleType string
// Fuel type
Fuel string
// Transmission type
TransmissionGear string
// Brand name
Brand string
// Vehicle model
Model string
// Vehicle model year
Year int
}
// Vehicle will generate a struct with vehicle information
func Vehicle() *VehicleInfo {
return &VehicleInfo{
VehicleType: VehicleType(),
Fuel: FuelType(),
TransmissionGear: TransmissionGearType(),
Brand: CarMaker(),
Model: CarModel(),
Year: Year(),
}
}
// VehicleType will generate a random vehicle type string
func VehicleType() string {
return getRandValue([]string{"vehicle", "vehicle_type"})
}
// FuelType will return a random fuel type
func FuelType() string {
return getRandValue([]string{"vehicle", "fuel_type"})
}
// TransmissionGearType will return a random transmission gear type
func TransmissionGearType() string {
return getRandValue([]string{"vehicle", "transmission_type"})
}
// CarMaker will return a random car maker
func CarMaker() string {
return getRandValue([]string{"vehicle", "maker"})
}
// CarModel will return a random car model
func CarModel() string {
return getRandValue([]string{"vehicle", "model"})
}

@ -0,0 +1,100 @@
package gofakeit
import (
"bytes"
"strings"
"unicode"
)
type paragrapOptions struct {
paragraphCount int
sentenceCount int
wordCount int
separator string
}
const bytesPerWordEstimation = 6
type sentenceGenerator func(wordCount int) string
type wordGenerator func() string
// Word will generate a random word
func Word() string {
return getRandValue([]string{"lorem", "word"})
}
// Sentence will generate a random sentence
func Sentence(wordCount int) string {
return sentence(wordCount, Word)
}
// Paragraph will generate a random paragraphGenerator
// Set Paragraph Count
// Set Sentence Count
// Set Word Count
// Set Paragraph Separator
func Paragraph(paragraphCount int, sentenceCount int, wordCount int, separator string) string {
return paragraphGenerator(paragrapOptions{paragraphCount, sentenceCount, wordCount, separator}, Sentence)
}
func sentence(wordCount int, word wordGenerator) string {
if wordCount <= 0 {
return ""
}
wordSeparator := ' '
sentence := bytes.Buffer{}
sentence.Grow(wordCount * bytesPerWordEstimation)
for i := 0; i < wordCount; i++ {
word := word()
if i == 0 {
runes := []rune(word)
runes[0] = unicode.ToTitle(runes[0])
word = string(runes)
}
sentence.WriteString(word)
if i < wordCount-1 {
sentence.WriteRune(wordSeparator)
}
}
sentence.WriteRune('.')
return sentence.String()
}
func paragraphGenerator(opts paragrapOptions, sentecer sentenceGenerator) string {
if opts.paragraphCount <= 0 || opts.sentenceCount <= 0 || opts.wordCount <= 0 {
return ""
}
//to avoid making Go 1.10 dependency, we cannot use strings.Builder
paragraphs := bytes.Buffer{}
//we presume the length
paragraphs.Grow(opts.paragraphCount * opts.sentenceCount * opts.wordCount * bytesPerWordEstimation)
wordSeparator := ' '
for i := 0; i < opts.paragraphCount; i++ {
for e := 0; e < opts.sentenceCount; e++ {
paragraphs.WriteString(sentecer(opts.wordCount))
if e < opts.sentenceCount-1 {
paragraphs.WriteRune(wordSeparator)
}
}
if i < opts.paragraphCount-1 {
paragraphs.WriteString(opts.separator)
}
}
return paragraphs.String()
}
// Question will return a random question
func Question() string {
return strings.Replace(HipsterSentence(Number(3, 10)), ".", "?", 1)
}
// Quote will return a random quote from a random person
func Quote() string {
return `"` + HipsterSentence(Number(3, 10)) + `" - ` + FirstName() + " " + LastName()
}

@ -56,6 +56,9 @@ github.com/benbjohnson/clock
github.com/beorn7/perks/quantile
# github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
github.com/bradfitz/gomemcache/memcache
# github.com/brianvoe/gofakeit v3.17.0+incompatible
github.com/brianvoe/gofakeit
github.com/brianvoe/gofakeit/data
# github.com/codegangsta/cli v1.20.0
github.com/codegangsta/cli
# github.com/davecgh/go-spew v1.1.1

Loading…
Cancel
Save