diff --git a/.travis.yml b/.travis.yml index 463ad3b..0e6d413 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ script: - go vet ./... - megacheck ./... - gocyclo -over 10 $(find . -iname '*.go' -type f | grep -v /vendor/) -# - golint -set_exit_status $(go list ./...) + - golint -set_exit_status $(go list ./... | grep -v mock) after_success: - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/auth/authentication.go b/auth/authentication.go index e251cfd..7c80c65 100644 --- a/auth/authentication.go +++ b/auth/authentication.go @@ -12,6 +12,7 @@ const ( headerSchema = "ApiKey " ) +// The Database interface for encapsulating database access. type Database interface { GetApplicationById(id string) *model.Application GetClientById(id string) *model.Client @@ -19,43 +20,49 @@ type Database interface { GetUserById(id uint) *model.User } +// Auth is the provider for authentication middleware type Auth struct { DB Database } type authenticate func(tokenId string, user *model.User) (success bool, userId uint) +// RequireAdmin returns a gin middleware which requires a client token or basic authentication header to be supplied +// with the request. Also the authenticated user must be an administrator. func (a *Auth) RequireAdmin() gin.HandlerFunc { return a.requireToken(func(tokenId string, user *model.User) (bool, uint) { if user != nil { - return user.Admin, user.Id + return user.Admin, user.ID } if token := a.DB.GetClientById(tokenId); token != nil { - return a.DB.GetUserById(token.UserId).Admin, token.UserId + return a.DB.GetUserById(token.UserID).Admin, token.UserID } return false, 0 }) } -func (a *Auth) RequireAll() gin.HandlerFunc { +// RequireClient returns a gin middleware which requires a client token or basic authentication header to be supplied +// with the request. +func (a *Auth) RequireClient() gin.HandlerFunc { return a.requireToken(func(tokenId string, user *model.User) (bool, uint) { if user != nil { - return true, user.Id + return true, user.ID } if token := a.DB.GetClientById(tokenId); token != nil { - return true, token.UserId + return true, token.UserID } return false, 0 }) } -func (a *Auth) RequireWrite() gin.HandlerFunc { +// RequireApplicationToken returns a gin middleware which requires an application token to be supplied with the request. +func (a *Auth) RequireApplicationToken() gin.HandlerFunc { return a.requireToken(func(tokenId string, user *model.User) (bool, uint) { if user != nil { return false, 0 } if token := a.DB.GetApplicationById(tokenId); token != nil { - return true, token.UserId + return true, token.UserID } return false, 0 }) @@ -96,8 +103,8 @@ func (a *Auth) requireToken(auth authenticate) gin.HandlerFunc { user := a.userFromBasicAuth(ctx) if user != nil || token != "" { - if ok, userId := auth(token, user); ok { - RegisterAuthentication(ctx, user, userId) + if ok, userID := auth(token, user); ok { + RegisterAuthentication(ctx, user, userID) ctx.Next() return } diff --git a/auth/authentication_test.go b/auth/authentication_test.go index 3cc1d7f..2907abf 100644 --- a/auth/authentication_test.go +++ b/auth/authentication_test.go @@ -26,15 +26,15 @@ func (s *AuthenticationSuite) SetupSuite() { gin.SetMode(gin.TestMode) s.DB = &authmock.MockDatabase{} s.auth = &Auth{s.DB} - s.DB.On("GetClientById", "clienttoken").Return(&model.Client{Id: "clienttoken", UserId: 1, Name: "android phone"}) - s.DB.On("GetClientById", "clienttoken_admin").Return(&model.Client{Id: "clienttoken", UserId: 2, Name: "android phone2"}) + s.DB.On("GetClientById", "clienttoken").Return(&model.Client{ID: "clienttoken", UserID: 1, Name: "android phone"}) + s.DB.On("GetClientById", "clienttoken_admin").Return(&model.Client{ID: "clienttoken", UserID: 2, Name: "android phone2"}) s.DB.On("GetClientById", mock.Anything).Return(nil) - s.DB.On("GetApplicationById", "apptoken").Return(&model.Application{Id: "apptoken", UserId: 1, Name: "backup server", Description: "irrelevant"}) - s.DB.On("GetApplicationById", "apptoken_admin").Return(&model.Application{Id: "apptoken", UserId: 2, Name: "backup server", Description: "irrelevant"}) + s.DB.On("GetApplicationById", "apptoken").Return(&model.Application{ID: "apptoken", UserID: 1, Name: "backup server", Description: "irrelevant"}) + s.DB.On("GetApplicationById", "apptoken_admin").Return(&model.Application{ID: "apptoken", UserID: 2, Name: "backup server", Description: "irrelevant"}) s.DB.On("GetApplicationById", mock.Anything).Return(nil) - s.DB.On("GetUserById", uint(1)).Return(&model.User{Id: 1, Name: "irrelevant", Admin: false}) - s.DB.On("GetUserById", uint(2)).Return(&model.User{Id: 2, Name: "irrelevant", Admin: true}) + s.DB.On("GetUserById", uint(1)).Return(&model.User{ID: 1, Name: "irrelevant", Admin: false}) + s.DB.On("GetUserById", uint(2)).Return(&model.User{ID: 2, Name: "irrelevant", Admin: true}) s.DB.On("GetUserByName", "existing").Return(&model.User{Name: "existing", Pass: CreatePassword("pw")}) s.DB.On("GetUserByName", "admin").Return(&model.User{Name: "admin", Pass: CreatePassword("pw"), Admin: true}) @@ -43,29 +43,29 @@ func (s *AuthenticationSuite) SetupSuite() { func (s *AuthenticationSuite) TestQueryToken() { // not existing token - s.assertQueryRequest("token", "ergerogerg", s.auth.RequireWrite, 401) - s.assertQueryRequest("token", "ergerogerg", s.auth.RequireAll, 401) + s.assertQueryRequest("token", "ergerogerg", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("token", "ergerogerg", s.auth.RequireClient, 401) s.assertQueryRequest("token", "ergerogerg", s.auth.RequireAdmin, 401) // not existing key - s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireWrite, 401) - s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireAll, 401) + s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireClient, 401) s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireAdmin, 401) // apptoken - s.assertQueryRequest("token", "apptoken", s.auth.RequireWrite, 200) - s.assertQueryRequest("token", "apptoken", s.auth.RequireAll, 401) + s.assertQueryRequest("token", "apptoken", s.auth.RequireApplicationToken, 200) + s.assertQueryRequest("token", "apptoken", s.auth.RequireClient, 401) s.assertQueryRequest("token", "apptoken", s.auth.RequireAdmin, 401) - s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireWrite, 200) - s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireAll, 401) + s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireApplicationToken, 200) + s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireClient, 401) s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireAdmin, 401) // clienttoken - s.assertQueryRequest("token", "clienttoken", s.auth.RequireWrite, 401) - s.assertQueryRequest("token", "clienttoken", s.auth.RequireAll, 200) + s.assertQueryRequest("token", "clienttoken", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("token", "clienttoken", s.auth.RequireClient, 200) s.assertQueryRequest("token", "clienttoken", s.auth.RequireAdmin, 401) - s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireWrite, 401) - s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireAll, 200) + s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireClient, 200) s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireAdmin, 200) } @@ -81,71 +81,71 @@ func (s *AuthenticationSuite) TestNothingProvided() { recorder := httptest.NewRecorder() ctx, _ := gin.CreateTestContext(recorder) ctx.Request = httptest.NewRequest("GET", "/", nil) - s.auth.RequireWrite()(ctx) + s.auth.RequireApplicationToken()(ctx) assert.Equal(s.T(), 401, recorder.Code) } func (s *AuthenticationSuite) TestHeaderApiKeyToken() { // not existing token - s.assertHeaderRequest("Authorization", "ApiKey ergerogerg", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "ApiKey ergerogerg", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "ApiKey ergerogerg", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "ApiKey ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ApiKey ergerogerg", s.auth.RequireAdmin, 401) // no authentication schema - s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireAdmin, 401) // wrong authentication schema - s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireAdmin, 401) // not existing key - s.assertHeaderRequest("Authorizationx", "ApiKey clienttoken", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorizationx", "ApiKey clienttoken", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorizationx", "ApiKey clienttoken", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorizationx", "ApiKey clienttoken", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorizationx", "ApiKey clienttoken", s.auth.RequireAdmin, 401) // apptoken - s.assertHeaderRequest("Authorization", "ApiKey apptoken", s.auth.RequireWrite, 200) - s.assertHeaderRequest("Authorization", "ApiKey apptoken", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "ApiKey apptoken", s.auth.RequireApplicationToken, 200) + s.assertHeaderRequest("Authorization", "ApiKey apptoken", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ApiKey apptoken", s.auth.RequireAdmin, 401) - s.assertHeaderRequest("Authorization", "ApiKey apptoken_admin", s.auth.RequireWrite, 200) - s.assertHeaderRequest("Authorization", "ApiKey apptoken_admin", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "ApiKey apptoken_admin", s.auth.RequireApplicationToken, 200) + s.assertHeaderRequest("Authorization", "ApiKey apptoken_admin", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ApiKey apptoken_admin", s.auth.RequireAdmin, 401) // clienttoken - s.assertHeaderRequest("Authorization", "ApiKey clienttoken", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "ApiKey clienttoken", s.auth.RequireAll, 200) + s.assertHeaderRequest("Authorization", "ApiKey clienttoken", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "ApiKey clienttoken", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "ApiKey clienttoken", s.auth.RequireAdmin, 401) - s.assertHeaderRequest("Authorization", "ApiKey clienttoken_admin", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "ApiKey clienttoken_admin", s.auth.RequireAll, 200) + s.assertHeaderRequest("Authorization", "ApiKey clienttoken_admin", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "ApiKey clienttoken_admin", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "ApiKey clienttoken_admin", s.auth.RequireAdmin, 200) } func (s *AuthenticationSuite) TestBasicAuth() { - s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireAdmin, 401) // user existing:pw - s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireAll, 200) + s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireAdmin, 401) // user admin:pw - s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireAll, 200) + s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireAdmin, 200) // user admin:pwx - s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireAdmin, 401) // user notexisting:pw - s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireWrite, 401) - s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireAll, 401) + s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireAdmin, 401) } diff --git a/auth/password.go b/auth/password.go index 9d47506..9e69420 100644 --- a/auth/password.go +++ b/auth/password.go @@ -4,6 +4,7 @@ import "golang.org/x/crypto/bcrypt" var strength = 13 +// CreatePassword returns a hashed version of the given password. func CreatePassword(pw string) []byte { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(pw), strength) if err != nil { @@ -12,6 +13,7 @@ func CreatePassword(pw string) []byte { return hashedPassword } +// ComparePassword compares a hashed password with its possible plaintext equivalent. func ComparePassword(hashedPassword, password []byte) bool { return bcrypt.CompareHashAndPassword(hashedPassword, password) == nil } diff --git a/auth/util.go b/auth/util.go index 90785e9..21b0e97 100644 --- a/auth/util.go +++ b/auth/util.go @@ -5,20 +5,22 @@ import ( "github.com/jmattheis/memo/model" ) -func RegisterAuthentication(ctx *gin.Context, user *model.User, userId uint) { +// RegisterAuthentication registers the user or the user id; The id can later be obtained by GetUserID. +func RegisterAuthentication(ctx *gin.Context, user *model.User, userID uint) { ctx.Set("user", user) - ctx.Set("userid", userId) + ctx.Set("userid", userID) } -func GetUserId(ctx *gin.Context) uint { +// GetUserID returns the user id which was previously registered by RegisterAuthentication. +func GetUserID(ctx *gin.Context) uint { user := ctx.MustGet("user").(*model.User) if user == nil { - userId := ctx.MustGet("userid").(uint) - if userId == 0 { + userID := ctx.MustGet("userid").(uint) + if userID == 0 { panic("token and user may not be null") } - return userId + return userID } - return user.Id + return user.ID } diff --git a/auth/util_test.go b/auth/util_test.go index 6d22fbd..9cdc6e1 100644 --- a/auth/util_test.go +++ b/auth/util_test.go @@ -22,16 +22,16 @@ func (s *UtilSuite) BeforeTest(suiteName, testName string) { } func (s *UtilSuite) Test_getId() { - s.expectUserIdWith(&model.User{Id: 2}, 0, 2) - s.expectUserIdWith(nil, 5, 5) + s.expectUserIDWith(&model.User{ID: 2}, 0, 2) + s.expectUserIDWith(nil, 5, 5) assert.Panics(s.T(), func() { - s.expectUserIdWith(nil, 0, 0) + s.expectUserIDWith(nil, 0, 0) }) } -func (s *UtilSuite) expectUserIdWith(user *model.User, tokenId uint, expectedId uint) { +func (s *UtilSuite) expectUserIDWith(user *model.User, tokenID uint, expectedID uint) { ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) - RegisterAuthentication(ctx, user, tokenId) - actualId := GetUserId(ctx) - assert.Equal(s.T(), expectedId, actualId) + RegisterAuthentication(ctx, user, tokenID) + actualID := GetUserID(ctx) + assert.Equal(s.T(), expectedID, actualID) } diff --git a/model/application.go b/model/application.go index 884608c..3349810 100644 --- a/model/application.go +++ b/model/application.go @@ -1,8 +1,9 @@ package model +// Application holds information about an app which can send notifications. type Application struct { - Id string `gorm:"primary_key;unique_index"` - UserId uint `gorm:"index" json:"-"` + ID string `gorm:"primary_key;unique_index"` + UserID uint `gorm:"index" json:"-"` Name string `form:"name" query:"name" json:"name" binding:"required"` Description string `form:"description" query:"description" json:"description"` Messages []Message `json:"-"` diff --git a/model/client.go b/model/client.go index 557eb17..5317645 100644 --- a/model/client.go +++ b/model/client.go @@ -1,7 +1,8 @@ package model +// The Client holds information about a device which can receive notifications (and other stuff). type Client struct { - Id string `gorm:"primary_key;unique_index"` - UserId uint `gorm:"index" json:"-"` + ID string `gorm:"primary_key;unique_index"` + UserID uint `gorm:"index" json:"-"` Name string `form:"name" query:"name" json:"name" binding:"required"` } diff --git a/model/message.go b/model/message.go index 49911c7..805677a 100644 --- a/model/message.go +++ b/model/message.go @@ -2,9 +2,10 @@ package model import "time" +// The Message holds information about a message which was sent by an Application. type Message struct { - Id uint `gorm:"AUTO_INCREMENT;primary_key;index"` - TokenId string + ID uint `gorm:"AUTO_INCREMENT;primary_key;index"` + TokenID string Message string Title string Priority int diff --git a/model/user.go b/model/user.go index 5d62da8..e6dd496 100644 --- a/model/user.go +++ b/model/user.go @@ -1,10 +1,11 @@ package model +// The User holds information about the credentials of a user and its application and client tokens. type User struct { - Id uint `gorm:"primary_key;unique_index;AUTO_INCREMENT"` - Name string - Pass []byte - Admin bool - Tokens []Application - Clients []Client + ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT"` + Name string + Pass []byte + Admin bool + Applications []Application + Clients []Client }