├── favicon.ico ├── Dockerfile ├── startApp.sh ├── gondalfConfig.json ├── setup.md ├── util_test.go ├── License.md ├── db_test.go ├── structs.go ├── scheduledJobs.go ├── server.go ├── dbStructs.go ├── Godeps └── Godeps.json ├── db.go ├── handlers.go ├── util.go ├── README.md ├── handlerUtils.go ├── handlerUtils_test.go └── coverage.html /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thezelus/gondalf/HEAD/favicon.ico -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.3.3-onbuild 2 | CMD ["./gondalf"] 3 | CMD bash -C "./startApp.sh" 4 | EXPOSE 3000 -------------------------------------------------------------------------------- /startApp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go run db.go dbStructs.go handlers.go handlerUtils.go structs.go util.go scheduledJobs.go server.go $1 4 | -------------------------------------------------------------------------------- /gondalfConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "dbUsername": "postgres", 3 | "dbPassword": "postgres", 4 | "dbHost": "localhost", 5 | "dbPort": "5432", 6 | "dbName": "gondalf", 7 | "dbSSLmode": "disable", 8 | "dbMaxIdleConnections": "10", 9 | "dbMaxOpenConnections": "100", 10 | "appPort":"3000", 11 | "appGracefulShutdownTimeinSeconds":"10", 12 | "appPropertiesRefreshTimeinMinutes":"10" 13 | } -------------------------------------------------------------------------------- /setup.md: -------------------------------------------------------------------------------- 1 | ###Project setup### 2 | 3 | go get github.com/go-martini/martini 4 | 5 | go get github.com/martini-contrib/binding 6 | 7 | go get github.com/jinzhu/gorm 8 | 9 | go get github.com/lib/pq 10 | 11 | go get github.com/gosimple/conf 12 | 13 | go get code.google.com/p/go-uuid/uuid 14 | 15 | go get code.google.com/p/go.crypto/bcrypt 16 | 17 | go get github.com/martini-contrib/render 18 | 19 | go get github.com/stretchr/graceful 20 | 21 | 22 | ####To run the server#### 23 | 24 | (For initializing db) 25 | 26 | go run *.go -initdb=true 27 | 28 | ####Test dependencies#### 29 | 30 | go get github.com/axw/gocov/gocov 31 | 32 | go get gopkg.in/matm/v1/gocov-html 33 | 34 | go get gopkg.in/check.v1 35 | 36 | To generate coverage report run "gocov test | gocov-html > coverage.html" 37 | 38 | 39 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | . "gopkg.in/check.v1" 6 | ) 7 | 8 | func TestUtilMethods(t *testing.T) { 9 | TestingT(t) 10 | } 11 | 12 | type UtilTestSuite struct{} 13 | 14 | var _ = Suite(&UtilTestSuite{}) 15 | 16 | func (suite *UtilTestSuite) SetUpSuite(c *C) { 17 | initDbFlag := false 18 | InitApp(testLogFile, &initDbFlag) 19 | TRACE.Println("UtilTestSuite Setup") 20 | } 21 | 22 | func (suite *UtilTestSuite) TearDownSuite(c *C) { 23 | TRACE.Println("UtilTestSuite TearDown") 24 | defer cleanUpAfterShutdown() 25 | } 26 | 27 | func (suite *UtilTestSuite) TestGetAppProperties(c *C) { 28 | TRACE.Println("Running test: TestGetAppProperties") 29 | 30 | webtimeOut, err := GetAppProperties("WebTimeOut") 31 | c.Assert(webtimeOut, Equals, "30") 32 | c.Assert(err, IsNil) 33 | 34 | invalidProperty, err := GetAppProperties("InvalidProperty") 35 | c.Assert(invalidProperty, Equals, "") 36 | c.Assert(err.Error(), Equals, "App property InvalidProperty not set") 37 | } 38 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Abhishek Sharma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /db_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | . "gopkg.in/check.v1" 6 | ) 7 | 8 | func TestDbMethods(t *testing.T) { 9 | TestingT(t) 10 | } 11 | 12 | type DbMethodsTestSuite struct{} 13 | 14 | var _ = Suite(&DbMethodsTestSuite{}) 15 | 16 | func (suite *DbMethodsTestSuite) SetUpSuite(c *C) { 17 | initDbFlag := false 18 | InitApp(testLogFile, &initDbFlag) 19 | TRACE.Println("DbMethodsTestSuite Setup") 20 | } 21 | 22 | func (suite *DbMethodsTestSuite) TearDownSuite(c *C) { 23 | TRACE.Println("DbMethodsTestSuite TearDown") 24 | defer cleanUpAfterShutdown() 25 | } 26 | 27 | func (suite *DbMethodsTestSuite) TestInsertAppPropertiesMultipleTimes(c *C) { 28 | TRACE.Println("Running test: TestInsertAppPropertiesMultipleTimes") 29 | tx := dbConnection.Begin() 30 | 31 | c.Check(InsertAppProperties(tx), Equals, true) 32 | 33 | c.Assert(InsertAppProperties(tx), Equals, true) 34 | 35 | tx.Rollback() 36 | } 37 | 38 | func (suite *DbMethodsTestSuite) TestInsertDeviceTypesMultipleTimes(c *C) { 39 | TRACE.Println("Running test: TestInsertDeviceTypesMultipleTimes") 40 | tx := dbConnection.Begin() 41 | 42 | c.Check(InsertDeviceTypes(tx), Equals, true) 43 | 44 | c.Assert(InsertDeviceTypes(tx), Equals, true) 45 | 46 | tx.Rollback() 47 | } 48 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type ConnectionParameters struct { 4 | username string 5 | password string 6 | host string 7 | port string 8 | dbname string 9 | sslmode string 10 | } 11 | 12 | type LoginCredential struct { 13 | Username string `json:"username" binding:"required"` 14 | Password string `json:"password" binding:"required"` 15 | DeviceId int `json:"deviceId" binding:"required"` 16 | } 17 | 18 | type CreateUserRequest struct { 19 | Username string `json:"username" binding:"required"` 20 | LegalName string `json:"legalname" binding:"required"` 21 | Password string `json:"password" binding:"required"` 22 | } 23 | 24 | type ValidateUsernameRequest struct { 25 | Username string `json:"username" binding:"required"` 26 | } 27 | 28 | type ChangePasswordRequest struct { 29 | Username string `json:"username" binding:"required"` 30 | OldPassword string `json:"oldPassword" binding:"required"` 31 | NewPassword string `json:"newPassword" binding:"required"` 32 | DeviceId int `json:"deviceId" binding:"required"` 33 | } 34 | 35 | type ValidateSessionTokenRequest struct { 36 | SessionToken string `json:"sessionToken" binding:"required"` 37 | } 38 | 39 | type CheckPermissionRequest struct { 40 | UserId int64 `json:"userId" binding:"required"` 41 | PermissionDescription string `json:"permissionDescription" binding:"required"` 42 | } 43 | 44 | type ErrorResponse struct { 45 | Status string `json:"status" binding:"required"` 46 | Message string `json:"message" binding:"required"` 47 | Description string `json:"description"` 48 | } 49 | -------------------------------------------------------------------------------- /scheduledJobs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | func StartScheduledJobs() { 10 | JobRefreshAppProperties() 11 | JobArchiveExpiredSessionToken() 12 | } 13 | 14 | func JobRefreshAppProperties() { 15 | 16 | LoadConfigurationFromFile() 17 | appPropertiesRefreshTimeinMinutes, err := strconv.Atoi(viper.GetString("appPropertiesRefreshTimeinMinutes")) 18 | if err != nil { 19 | ERROR.Panicln("appPropertiesRefreshTimeinMinutes missing from the config file") 20 | } 21 | 22 | ticker := time.NewTicker(time.Duration(appPropertiesRefreshTimeinMinutes) * time.Minute) 23 | TRACE.Println("Starting goroutine for refreshing app properties") 24 | go func() { 25 | for { 26 | select { 27 | case <-ticker.C: 28 | LoadAppPropertiesFromDb() 29 | case <-quit: 30 | TRACE.Println("Quit signal: JobRefreshAppProperties") 31 | ticker.Stop() 32 | } 33 | } 34 | }() 35 | } 36 | 37 | func JobArchiveExpiredSessionToken() { 38 | 39 | tokenCleanUpFrequencyString, err := GetAppProperties("TokenCleanUpFrequency") 40 | if err != nil { 41 | ERROR.Println(err.Error()) 42 | } 43 | 44 | tokenCleanUpFrequencyInteger, err := strconv.Atoi(tokenCleanUpFrequencyString) 45 | if err != nil { 46 | ERROR.Println("String to integer conversion failed for TokenCleanUpFrequency reverting to default value of 180 minutes") 47 | tokenCleanUpFrequencyInteger = 180 48 | } 49 | 50 | ticker := time.NewTicker(time.Duration(tokenCleanUpFrequencyInteger) * time.Minute) 51 | TRACE.Println("Starting goroutine for token archiving") 52 | go func() { 53 | for { 54 | select { 55 | case <-ticker.C: 56 | ArchiveTokenAfterCutOffTime(&dbConnection) 57 | case <-quit: 58 | TRACE.Println("Quit signal: JobArchiveExpiredSessionToken") 59 | } 60 | } 61 | }() 62 | 63 | } 64 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/go-martini/martini" 10 | "github.com/jinzhu/gorm" 11 | "github.com/martini-contrib/binding" 12 | "github.com/martini-contrib/render" 13 | "github.com/spf13/viper" 14 | "github.com/stretchr/graceful" 15 | ) 16 | 17 | var ( 18 | dbConnection gorm.DB 19 | configName = "gondalfConfig" 20 | logFile = "gondalf.log" 21 | testLogFile = "testLogs.log" 22 | properties []AppProperties 23 | file *os.File 24 | quit = make(chan struct{}) 25 | ) 26 | 27 | func main() { 28 | 29 | initializeDB := flag.Bool("initdb", false, "initalizing database") 30 | flag.Parse() 31 | 32 | InitApp(logFile, initializeDB) 33 | 34 | m := martini.Classic() 35 | m.Use(render.Renderer()) 36 | m.Use(martini.Recovery()) 37 | 38 | //status 39 | m.Get("/status", StatusHandler) 40 | 41 | //Login end point 42 | m.Post("/auth/login", binding.Bind(LoginCredential{}), LoginHandler) 43 | 44 | //Create new user 45 | m.Post("/user/create", binding.Bind(CreateUserRequest{}), CreateUserHandler) 46 | 47 | //Validate unique username 48 | m.Post("/validate/username", binding.Bind(ValidateUsernameRequest{}), ValidateUsernameHandler) 49 | 50 | //Validate session token 51 | m.Post("/validate/token", binding.Bind(ValidateSessionTokenRequest{}), ValidateSessionTokenHandler) 52 | 53 | //Change password 54 | m.Post("/user/changePassword", binding.Bind(ChangePasswordRequest{}), ChangePasswordHandler) 55 | 56 | //Check permission 57 | m.Post("/user/checkPermission", binding.Bind(CheckPermissionRequest{}), CheckPermissionsForUserHandler) 58 | 59 | appGracefulShutdownTimeinSeconds, err := strconv.Atoi(viper.GetString("appGracefulShutdownTimeinSeconds")) 60 | if err != nil { 61 | ERROR.Panicln("Cannot start the server, shutdown time missing from config file") 62 | } 63 | 64 | graceful.Run(":"+viper.GetString("appPort"), time.Duration(appGracefulShutdownTimeinSeconds)*time.Second, m) 65 | 66 | defer cleanUpAfterShutdown() 67 | } 68 | 69 | func cleanUpAfterShutdown() { 70 | //TRACE.Println("Sending kill signal to all routines by closing quit channel") 71 | //close(quit) 72 | 73 | TRACE.Println("Cleaning up dbConnection and file stream") 74 | dbConnection.Close() 75 | file.Close() 76 | } 77 | -------------------------------------------------------------------------------- /dbStructs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | type User struct { 6 | Id int64 7 | UserName string `sql:"size:256;not null;unique"` 8 | LegalName string `sql:"size:256;not null"` 9 | Password string `sql:"not null"` 10 | UpdatedAt time.Time `sql:"not null"` 11 | Active bool `sql:"not null"` 12 | } 13 | 14 | type Permission struct { 15 | Id int64 16 | PermissionDescription string `sql:"not null; size: 512"` 17 | } 18 | 19 | type Group struct { 20 | Id int64 21 | GroupDescription string `sql:"not null; size: 512"` 22 | } 23 | 24 | type GroupPermission struct { 25 | Id int64 26 | GroupId int64 `sql:"not null"` 27 | PermissionId int64 `sql:"not null"` 28 | } 29 | 30 | type UserGroup struct { 31 | Id int64 32 | UserId int64 `sql:"not null"` 33 | GroupId int64 `sql:"not null"` 34 | } 35 | 36 | type AppProperties struct { 37 | Id int64 38 | PropertyName string `sql:"not null;unique"` 39 | PropertyValue string `sql:"type:varchar(256);not null"` 40 | UpdatedAt time.Time `sql:"not null"` 41 | } 42 | 43 | type ActivityLog struct { 44 | Id int64 45 | UserId int64 `sql:"not null"` 46 | TokenId int64 `sql:"not null"` 47 | ActivityTime time.Time `sql:"not null"` 48 | Event string `sql:"size:255"` 49 | } 50 | 51 | type Token struct { 52 | Id int64 53 | Token string `sql:"type:varchar(256);not null;unique"` 54 | UserId int64 55 | Key string `sql:"type:varchar(256);not null"` 56 | CreatedAt time.Time 57 | LastAccessedAt time.Time 58 | ExpiresAt time.Time 59 | DeviceTypeId int 60 | Active bool `sql:not null` 61 | } 62 | 63 | type ArchivedToken struct { 64 | Id int64 65 | Token string `sql:"type:varchar(256);not null;unique"` 66 | UserId int64 67 | Key string `sql:"type:varchar(256);not null"` 68 | CreatedAt time.Time 69 | LastAccessedAt time.Time 70 | ExpiresAt time.Time 71 | DeviceTypeId int 72 | Active bool `sql:not null` 73 | } 74 | 75 | type DeviceType struct { 76 | Id int 77 | Device string `sql:"not null"` 78 | DeviceCode int `sql:"not null;unique"` 79 | } 80 | 81 | type PasswordRecord struct { 82 | Id int 83 | UserId int64 84 | LoginCount int 85 | CreatedAt time.Time 86 | UpdatedAt time.Time 87 | } 88 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "gondalf", 3 | "GoVersion": "go1.3.3", 4 | "Deps": [ 5 | { 6 | "ImportPath": "code.google.com/p/go-uuid/uuid", 7 | "Comment": "null-12", 8 | "Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9" 9 | }, 10 | { 11 | "ImportPath": "code.google.com/p/go.crypto/bcrypt", 12 | "Comment": "null-212", 13 | "Rev": "1064b89a6fb591df0dd65422295b8498916b092f" 14 | }, 15 | { 16 | "ImportPath": "code.google.com/p/go.crypto/blowfish", 17 | "Comment": "null-212", 18 | "Rev": "1064b89a6fb591df0dd65422295b8498916b092f" 19 | }, 20 | { 21 | "ImportPath": "github.com/BurntSushi/toml", 22 | "Comment": "v0.1.0", 23 | "Rev": "2ceedfee35ad3848e49308ab0c9a4f640cfb5fb2" 24 | }, 25 | { 26 | "ImportPath": "github.com/codegangsta/inject", 27 | "Comment": "v1.0-rc1", 28 | "Rev": "37d7f8432a3e684eef9b2edece76bdfa6ac85b39" 29 | }, 30 | { 31 | "ImportPath": "github.com/go-martini/martini", 32 | "Comment": "v1.0-3-gadaa8f7", 33 | "Rev": "adaa8f7fe1224a8f0fac72d23ee80c149b21f0fb" 34 | }, 35 | { 36 | "ImportPath": "github.com/jinzhu/gorm", 37 | "Rev": "744cb7dfda9ee8c681dc17b540cf8db9fa74f726" 38 | }, 39 | { 40 | "ImportPath": "github.com/kr/pretty", 41 | "Comment": "go.weekly.2011-12-22-24-gf31442d", 42 | "Rev": "f31442d60e51465c69811e2107ae978868dbea5c" 43 | }, 44 | { 45 | "ImportPath": "github.com/kr/text", 46 | "Rev": "6807e777504f54ad073ecef66747de158294b639" 47 | }, 48 | { 49 | "ImportPath": "github.com/lib/pq", 50 | "Rev": "50a04ed4e5268fb34ca6d885202b569ef89c0a7a" 51 | }, 52 | { 53 | "ImportPath": "github.com/martini-contrib/binding", 54 | "Rev": "f506d6896ca9a8d797ab6b3744f74c3bcd05eb7a" 55 | }, 56 | { 57 | "ImportPath": "github.com/martini-contrib/render", 58 | "Rev": "c8d058a00a82f27bfc8a49d62525b7c60efcf5b8" 59 | }, 60 | { 61 | "ImportPath": "github.com/mitchellh/mapstructure", 62 | "Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf" 63 | }, 64 | { 65 | "ImportPath": "github.com/spf13/cast", 66 | "Rev": "770890fb156e3654a3aaa7e696971f7e5a73df4a" 67 | }, 68 | { 69 | "ImportPath": "github.com/spf13/jwalterweatherman", 70 | "Rev": "e3682f3b5526cf86abc2d415aa312cd5531e3d0a" 71 | }, 72 | { 73 | "ImportPath": "github.com/spf13/pflag", 74 | "Rev": "463bdc838f2b35e9307e91d480878bda5fff7232" 75 | }, 76 | { 77 | "ImportPath": "github.com/spf13/viper", 78 | "Rev": "83fd92627cc9834ceccb85544b1fab7bf52150a0" 79 | }, 80 | { 81 | "ImportPath": "github.com/stretchr/graceful", 82 | "Rev": "1302e8f6efee4be1ed63e592b4a8a95a245a66ba" 83 | }, 84 | { 85 | "ImportPath": "gopkg.in/check.v1", 86 | "Rev": "871360013c92e1c715c2de6d06b54899468a8a2d" 87 | }, 88 | { 89 | "ImportPath": "gopkg.in/yaml.v1", 90 | "Rev": "9f9df34309c04878acc86042b16630b0f696e1de" 91 | } 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "github.com/jinzhu/gorm" 8 | _ "github.com/lib/pq" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | //Method to initialize by automigrating new tables and adding columns to old ones. 13 | //This method doesn't change any existing column types or delete columns. 14 | //Corresponding DB structures can be found in dbStructs.go 15 | //App properties entries are also initialized here 16 | func InitDB() { 17 | 18 | dbConnection.AutoMigrate(User{}) 19 | dbConnection.AutoMigrate(Permission{}) 20 | dbConnection.AutoMigrate(Group{}) 21 | dbConnection.AutoMigrate(GroupPermission{}) 22 | dbConnection.AutoMigrate(UserGroup{}) 23 | 24 | dbConnection.AutoMigrate(AppProperties{}) 25 | dbConnection.AutoMigrate(ActivityLog{}) 26 | 27 | dbConnection.AutoMigrate(Token{}) 28 | dbConnection.AutoMigrate(ArchivedToken{}) 29 | dbConnection.AutoMigrate(DeviceType{}) 30 | dbConnection.AutoMigrate(PasswordRecord{}) 31 | 32 | InsertAppProperties(&dbConnection) 33 | InsertDeviceTypes(&dbConnection) 34 | } 35 | 36 | func InsertAppProperties(db *gorm.DB) bool { 37 | 38 | webTimeOut := AppProperties{PropertyName: "WebTimeOut", PropertyValue: "30", UpdatedAt: time.Now().UTC()} 39 | mobileTimeOut := AppProperties{PropertyName: "MobileTimeOut", PropertyValue: "720", UpdatedAt: time.Now().UTC()} 40 | dbDebugLogs := AppProperties{PropertyName: "DbDebugLogs", PropertyValue: "false", UpdatedAt: time.Now().UTC()} 41 | timeExtension := AppProperties{PropertyName: "TimeExtension", PropertyValue: "5", UpdatedAt: time.Now().UTC()} 42 | tokenCutOffTime := AppProperties{PropertyName: "TokenCutOffTime", PropertyValue: "30", UpdatedAt: time.Now().UTC()} 43 | tokenCleanUpFrequency := AppProperties{PropertyName: "TokenCleanUpFrequency", PropertyValue: "180", UpdatedAt: time.Now().UTC()} 44 | 45 | propertiesSlice := []AppProperties{webTimeOut, mobileTimeOut, dbDebugLogs, timeExtension, tokenCutOffTime, tokenCleanUpFrequency} 46 | 47 | for i := range propertiesSlice { 48 | existsErr := db.Where(&AppProperties{PropertyName: propertiesSlice[i].PropertyName}).Find(&AppProperties{}).Error 49 | if existsErr == gorm.RecordNotFound { 50 | db.Save(&propertiesSlice[i]) 51 | } 52 | } 53 | 54 | return true 55 | } 56 | 57 | func InsertDeviceTypes(db *gorm.DB) bool { 58 | webDevice := DeviceType{Device: "web", DeviceCode: web} 59 | mobileDevice := DeviceType{Device: "mobile", DeviceCode: mobile} 60 | 61 | devicesSlice := []DeviceType{webDevice, mobileDevice} 62 | 63 | for i := range devicesSlice { 64 | existsErr := db.Where(&devicesSlice[i]).Find(&DeviceType{}).Error 65 | if existsErr == gorm.RecordNotFound { 66 | db.Save(&devicesSlice[i]) 67 | } 68 | } 69 | 70 | return true 71 | } 72 | 73 | //Returns a database connection with connection pooling 74 | func GetDBConnection() gorm.DB { 75 | 76 | LoadConfigurationFromFile() 77 | 78 | var connParam ConnectionParameters 79 | 80 | connParam.username = viper.GetString("dbUsername") 81 | connParam.password = viper.GetString("dbPassword") 82 | connParam.host = viper.GetString("dbHost") 83 | connParam.port = viper.GetString("dbPort") 84 | connParam.dbname = viper.GetString("dbName") 85 | connParam.sslmode = viper.GetString("dbSSLmode") 86 | 87 | source := "user=" + connParam.username + 88 | " password=" + connParam.password + 89 | " dbname=" + connParam.dbname + 90 | " port=" + connParam.port + 91 | " host=" + connParam.host + 92 | " sslmode=" + connParam.sslmode 93 | 94 | db, err := gorm.Open("postgres", source) 95 | 96 | if err != nil { 97 | ERROR.Panicln("Error opening DB connection") 98 | } 99 | 100 | TRACE.Println("DB Connection opened") 101 | 102 | maxIdleConnections, _ := strconv.Atoi(viper.GetString("dbMaxIdleConnections")) 103 | maxOpenConnections, _ := strconv.Atoi(viper.GetString("dbMaxOpenConnections")) 104 | 105 | db.DB().SetMaxIdleConns(maxIdleConnections) 106 | db.DB().SetMaxOpenConns(maxOpenConnections) 107 | db.DB().Ping() 108 | 109 | return db 110 | } 111 | -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/martini-contrib/render" 5 | ) 6 | 7 | //Returns current server status 8 | func StatusHandler(r render.Render) { 9 | r.JSON(200, map[string]interface{}{"status": Status()}) 10 | } 11 | 12 | //Logins a user by checking login credentials, returns a UserToken if login is successful 13 | func LoginHandler(login LoginCredential, r render.Render) { 14 | 15 | authenticateUserErr := AuthenticateUser(login.Username, login.Password, &dbConnection) 16 | 17 | if authenticateUserErr == nil { 18 | sessionToken, err := CreateNewTokenDbEntry(login, &dbConnection) 19 | if err != nil { 20 | ERROR.Println(DatabaseError.Error() + " :" + login.Username) 21 | response := ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""} 22 | r.JSON(500, response) 23 | } else { 24 | r.JSON(200, map[string]interface{}{"sessionToken": sessionToken}) 25 | } 26 | } else if authenticateUserErr == FirstLoginPasswordChange { 27 | response := ErrorResponse{Status: "Forbidden", Message: FirstLoginPasswordChange.Error(), Description: ""} 28 | r.JSON(403, response) 29 | } else { 30 | response := ErrorResponse{Status: "Unauthorized", Message: AuthenticationFailed.Error(), Description: ""} 31 | r.JSON(401, response) 32 | } 33 | } 34 | 35 | func ValidateUsernameHandler(usernameRequest ValidateUsernameRequest, r render.Render) { 36 | 37 | validUsernameFlag := ValidateUniqueUsername(usernameRequest.Username, &dbConnection) 38 | 39 | r.JSON(200, map[string]interface{}{"valid": validUsernameFlag}) 40 | } 41 | 42 | func CreateUserHandler(request CreateUserRequest, r render.Render) { 43 | 44 | status, err := CreateNewUser(request, &dbConnection) 45 | if err != nil { 46 | ERROR.Println(err.Error()) 47 | var response ErrorResponse 48 | if status == 500 { 49 | response = ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""} 50 | } else if status == 409 { 51 | response = ErrorResponse{Status: "Conflict", Message: err.Error(), Description: ""} 52 | } 53 | r.JSON(status, response) 54 | } else { 55 | r.JSON(status, map[string]interface{}{"userCreated": true}) 56 | } 57 | } 58 | 59 | func ChangePasswordHandler(request ChangePasswordRequest, r render.Render) { 60 | 61 | authenticateUserError := AuthenticateUser(request.Username, request.OldPassword, &dbConnection) 62 | if authenticateUserError == nil || authenticateUserError == FirstLoginPasswordChange { 63 | status, changePasswordError := ChangePassword(request.Username, request.NewPassword, &dbConnection) 64 | if changePasswordError == nil { 65 | login := LoginCredential{Username: request.Username, Password: request.NewPassword, DeviceId: request.DeviceId} 66 | sessionToken, err := CreateNewTokenDbEntry(login, &dbConnection) 67 | if err == nil { 68 | r.JSON(200, map[string]interface{}{"passwordChanged": true, "sessionToken": sessionToken}) 69 | } else { 70 | ERROR.Println("DB entry failed for token, returning 500") 71 | response := ErrorResponse{Status: "Internal Server Error", Message: "Password changed with but encountered: " + SystemError.Error(), Description: ""} 72 | r.JSON(500, response) 73 | } 74 | } else { 75 | var response ErrorResponse 76 | if status == 401 { 77 | response = ErrorResponse{Status: "Unauthorized", Message: AuthenticationFailed.Error(), Description: ""} 78 | } else if status == 500 { 79 | response = ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""} 80 | } 81 | r.JSON(status, response) 82 | } 83 | } else { 84 | response := ErrorResponse{Status: "Unauthorized", Message: AuthenticationFailed.Error(), Description: ""} 85 | r.JSON(401, response) 86 | } 87 | 88 | } 89 | 90 | func ValidateSessionTokenHandler(request ValidateSessionTokenRequest, r render.Render) { 91 | 92 | err, userId := ValidateSessionToken(request.SessionToken, &dbConnection) 93 | 94 | if err == nil { 95 | r.JSON(200, map[string]interface{}{"userId": userId}) 96 | } else { 97 | ERROR.Println("Error validating sessionToken: " + request.SessionToken + ", error:" + err.Error()) 98 | if err == InvalidSessionToken { 99 | response := ErrorResponse{Status: "Unauthorized", Message: InvalidSessionToken.Error(), Description: ""} 100 | r.JSON(401, response) 101 | } else if err == ExpiredSessionToken { 102 | response := ErrorResponse{Status: "Forbidden", Message: ExpiredSessionToken.Error(), Description: ""} 103 | r.JSON(403, response) 104 | } else { 105 | response := ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""} 106 | r.JSON(500, response) 107 | } 108 | } 109 | } 110 | 111 | func CheckPermissionsForUserHandler(request CheckPermissionRequest, r render.Render) { 112 | 113 | err := CheckPermissionsForUser(request.UserId, Permission{PermissionDescription: request.PermissionDescription}, &dbConnection) 114 | 115 | if err == nil { 116 | r.JSON(200, map[string]interface{}{"permissionCheck": true}) 117 | } else if err == PermissionDenied { 118 | response := ErrorResponse{Status: "Unauthorized", Message: PermissionDenied.Error(), Description: ""} 119 | r.JSON(401, response) 120 | } else { 121 | response := ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""} 122 | r.JSON(500, response) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/jinzhu/gorm" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | //Error types: 17 | // 18 | //Invalid Session Token 19 | //Expired Session Token 20 | //Unregistered User 21 | //Invalid Password 22 | //First Login Change Password 23 | //Authentication Failed 24 | //Encryption Failed 25 | //Database Error 26 | //Permission Denied 27 | 28 | var ( 29 | TRACE *log.Logger 30 | INFO *log.Logger 31 | WARNING *log.Logger 32 | ERROR *log.Logger 33 | DATABASE *log.Logger 34 | 35 | InvalidSessionToken = errors.New("Invalid Session Token") 36 | ExpiredSessionToken = errors.New("Expired Session Token") 37 | UnregisteredUser = errors.New("Unregistered User") 38 | IncorrectPassword = errors.New("Invalid Password") 39 | FirstLoginPasswordChange = errors.New("First Login Change Password") 40 | AuthenticationFailed = errors.New("Authentication Failed") 41 | EncryptionError = errors.New("Encryption Failed") 42 | DatabaseError = errors.New("Database Error") 43 | PermissionDenied = errors.New("Permission Denied") 44 | SystemError = errors.New("System Error") 45 | DuplicateUsernameError = errors.New("Duplicate username") 46 | ) 47 | 48 | //Constant values: 49 | //For web use 1 50 | //For mobile use 2 51 | const ( 52 | web = 1 53 | mobile = 2 54 | 55 | LOGIN = "LOGIN" 56 | PASSWORD_CHANGE = "PASSWORD_CHANGE" 57 | ) 58 | 59 | //Initializes the app by setting up logging file, defaults to stdout in case of error opening the specified file. 60 | //Opens the DB connection 61 | //Initialized DB with tables if -initdb=true option is passed when starting the app 62 | func InitApp(logFileName string, initializeDB *bool) { 63 | var err error 64 | file, err = os.OpenFile(logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) 65 | if err != nil { 66 | fmt.Println("Log file cannot be opened") 67 | file.Close() 68 | file = os.Stdout 69 | } 70 | 71 | InitLogger(file, file, file, file, file) 72 | TRACE.Println("Logger initialized to file " + logFileName) 73 | 74 | dbConnection = GetDBConnection() 75 | TRACE.Println("Global connection object initialized") 76 | 77 | TRACE.Println(string(strconv.AppendBool([]byte("Application started with Init DB flag value "), *initializeDB))) 78 | 79 | if *initializeDB == true { 80 | TRACE.Println("DB Init Start") 81 | InitDB() 82 | TRACE.Println("DB Init Complete") 83 | } 84 | 85 | LoadAppPropertiesFromDb() 86 | 87 | dbLoggerPropertyValue, err := GetAppProperties("DbDebugLogs") 88 | 89 | if err == nil { 90 | dbLoggerFlag, err := strconv.ParseBool(dbLoggerPropertyValue) 91 | if err == nil && dbLoggerFlag { 92 | dbConnection.LogMode(dbLoggerFlag) 93 | dbConnection.SetLogger(DATABASE) 94 | TRACE.Println("Database logger initialized") 95 | } 96 | } 97 | 98 | StartScheduledJobs() 99 | } 100 | 101 | func InitLogger(traceHandle io.Writer, infoHandle io.Writer, warningHandle io.Writer, errorHandle io.Writer, databaseHandle io.Writer) { 102 | 103 | TRACE = log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile) 104 | 105 | INFO = log.New(infoHandle, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile) 106 | 107 | WARNING = log.New(warningHandle, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile) 108 | 109 | ERROR = log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile) 110 | 111 | DATABASE = log.New(databaseHandle, "DATABASE: ", log.Ldate|log.Ltime|log.Lshortfile) 112 | } 113 | 114 | func GetAppProperties(propertyName string) (string, error) { 115 | for index := range properties { 116 | if properties[index].PropertyName == propertyName { 117 | return properties[index].PropertyValue, nil 118 | } 119 | } 120 | return "", errors.New("App property " + propertyName + " not set") 121 | } 122 | 123 | func LoadConfigurationFromFile() { 124 | TRACE.Println("Loading configuration from " + configName) 125 | 126 | viper.SetConfigName(configName) 127 | viper.AddConfigPath(".") 128 | err := viper.ReadInConfig() 129 | 130 | if err != nil { 131 | panic("Configuration couldn't be initialized, panicking now " + err.Error()) 132 | } 133 | } 134 | 135 | func LoadAppPropertiesFromDb() { 136 | dbConnection.Find(&properties) 137 | TRACE.Println("App properties array initialized at: " + time.Now().String()) 138 | } 139 | 140 | func ArchiveTokenAfterCutOffTime(db *gorm.DB) { 141 | 142 | tokenCutOffTimeString, err := GetAppProperties("TokenCutOffTime") 143 | if err != nil { 144 | ERROR.Println(err.Error()) 145 | } 146 | 147 | tokenCutOffTimeInteger, err := strconv.Atoi(tokenCutOffTimeString) 148 | if err != nil { 149 | ERROR.Println("String to integer conversion failed for TokenCutOffTime, reverting to default value = 30 minutes") 150 | tokenCutOffTimeInteger = 30 151 | } 152 | 153 | TRACE.Println("Token archiving started at: " + time.Now().String()) 154 | 155 | db.Exec("UPDATE TABLE tokens SET active = ? WHERE (SELECT EXTRACT(MINUTE FROM (? - expires_at)) FROM tokens) > ?", false, time.Now().UTC(), tokenCutOffTimeInteger) 156 | 157 | db.Exec("INSERT INTO archived_tokens (token, user_id, key, created_at, last_accessed_at, expires_at, device_type_id, active)(SELECT (token, user_id, key, created_at, last_accessed_at, expires_at, device_type_id, active) WHERE active = ?)", false) 158 | 159 | db.Exec("DELETE FROM tokens WHERE active = ?", false) 160 | 161 | TRACE.Println("Token archiving completed at: " + time.Now().String()) 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Please feel free to create a new issue if you come across one or want a new feature to be added. I am looking for contributors, feel free to send pull requests. 2 | 3 | ##What is Gondalf?## 4 | Gondalf is a ready to deploy microservice that provides user management, authentication, and role based authorization features out of box. Gondalf is built using [martini](https://github.com/go-martini/martini) and [gorm](https://github.com/jinzhu/gorm), and uses [postgresql](http://www.postgresql.org) as the default database. 5 | 6 | ##Features:## 7 | 8 | ###1. User management### 9 | - User creation 10 | - Validating unique username 11 | - Password change on first login 12 | - Encrypted password storage 13 | - Activity logs 14 | 15 | ###2. Authentication### 16 | - User authentication 17 | - Token-based authentication 18 | - Custom token expiry and renewal times 19 | 20 | ###3. Authorization### 21 | - Role-based authorization including group permissions 22 | 23 | 24 | ##Why Gondalf?## 25 | Over the course of multiple projects I realized that there are some common features that can be packed into a single microservice and can be used right out of the box. Gondalf is the first piece in that set. 26 | 27 | 28 | ##TODO List## 29 | - [X] Add end points for permission checking 30 | - [X] Input timeout values and server port from config file 31 | - [X] Refresh app properties from DB after fixed interval 32 | - [X] Add a cron job for cleaning up and archiving expired session tokens to keep the validation request latency low 33 | - [X] Dockerize gondalf 34 | - [X] Refactored the API to include a consistent error payload 35 | - [ ] Add more events to Activity Logs 36 | - [ ] Improve documentation - add details about logging, app properties, and configuration 37 | - [ ] Switch to [negroni](https://github.com/codegangsta/negroni) and [gorilla mux](http://www.gorillatoolkit.org/pkg/mux) 38 | - [ ] Add TLS support for the end point 39 | - [ ] Provide one click deploy solution 40 | - [ ] Add CI on checkins 41 | - [ ] Add support for other databases 42 | 43 | 44 | ###Why call it *Gondalf* ?### 45 | 46 | Because and it is Go, so why not both? 47 | 48 | 49 | 50 | ##Installation Instructions:## 51 | 52 | - Clone the repository in a local directory 53 | 54 | - Database configuration can be set under the the config file named gondalf.config 55 | 56 | - Gondalf creates required tables using the configuration provided in the config file. For this the 57 | initdb flag should be set to true when starting the app. 58 | 59 | `$ bash startApp.sh -initdb=true` 60 | 61 | ###App Properties### 62 | 63 | These are set in a table called "app_properties" and are initially set to default values when the application is started with "intidb" flag. 64 | 65 | - *WebTimeOut* - Defines the inactivity time after which a session token created from web login is considered expired, default value is 30 minutes 66 | - *MobileTimeOut* - Defines the inactivity time after which a session token created from mobile login is considered expired, default value is 720 minutes 67 | - *TimeExtension* - Defines the time extension provided to a session token (web or mobile) if it is validated with less than 5 minutes of life time remaining. This is done to avoid stale tokens lingering around on devices. Default set to 5 minutes. 68 | - *DbDebugLogs* - Flag for turning on printing of database debugging logs, default value is false. 69 | - *TokenCutOffTime* - Defines the time after which an expired token will be marked for clean-up. Default value is 30 minutes. 70 | - *TokenCleanUpFrequency* - Defines the frequency of clean-up i.e. time between scheduler execution. Default value is 180 minutes. 71 | 72 | ##Request and Response formats## 73 | 74 | ###Error Codes### 75 | 76 | - Invalid Session Token 77 | - Expired Session Token 78 | - Unregistered User 79 | - Invalid Password 80 | - First Login Change Password 81 | - Authentication Failed 82 | - Encryption Failed 83 | - Database Error 84 | - Permission Denied 85 | - System Error 86 | - Duplicate Username Error 87 | 88 | ###LoginCredential### 89 | 90 | ####Request#### 91 | 92 | ```javascript 93 | { 94 | "username": "test2User", 95 | "password" : "testPassword", 96 | "deviceId" : 1 97 | } 98 | ``` 99 | 100 | deviceId code 1 for web, 2 for mobile 101 | 102 | ####Response#### 103 | 104 | ```javascript 105 | { 106 | "sessionToken": "testSessionToken", 107 | } 108 | ``` 109 | 110 | ###ValidateUsername### 111 | 112 | For validating unique username 113 | 114 | ####Request#### 115 | 116 | ```javascript 117 | { 118 | "username": "test2User" 119 | } 120 | ``` 121 | 122 | ####Response#### 123 | 124 | ```javascript 125 | { 126 | "valid": true 127 | } 128 | ``` 129 | 130 | ###CreateUser### 131 | 132 | ####Request#### 133 | 134 | ```javascript 135 | { 136 | "username": "test2User", 137 | "legalname": "testLegalName", 138 | "password": "testPassword" 139 | } 140 | ``` 141 | ####Response#### 142 | 143 | ```javascript 144 | { 145 | "userCreated": true 146 | } 147 | ``` 148 | 149 | ###Change password### 150 | 151 | ####Request#### 152 | 153 | ```javascript 154 | { 155 | "username": "test2User", 156 | "oldPassword": "testOldPassword", 157 | "newPassword": "newTestPassword", 158 | "deviceId": 1 159 | } 160 | ``` 161 | 162 | ####Response#### 163 | 164 | If the old credentials are correct then: 165 | 166 | ```javascript 167 | { 168 | "passwordChanged": true, 169 | "sessionToken": "testSessionToken" 170 | } 171 | ``` 172 | 173 | ###Validate Session Token### 174 | 175 | ####Request#### 176 | 177 | ```javascript 178 | { 179 | "sessionToken": "testSessionToken" 180 | } 181 | ``` 182 | 183 | ####Response#### 184 | 185 | ```javascript 186 | { 187 | "userId": 1234 188 | } 189 | ``` 190 | 191 | ###Permission Checking### 192 | 193 | ####Request#### 194 | 195 | ```javascript 196 | { 197 | "userId": 123456, 198 | "permissionDescription" : "ADMIN" 199 | } 200 | ```` 201 | 202 | ####Response#### 203 | 204 | ```javascript 205 | { 206 | "permissionResult" : true 207 | } 208 | ``` 209 | 210 | ###Error Response### 211 | 212 | ```javascript 213 | { 214 | "status": "Internal Server Error" / "Unauthorized" / "Conflict" / "Forbidden", 215 | "message": "Invalid Session Token" / "Expired Session Token" etc., 216 | "description": "" 217 | } 218 | ``` -------------------------------------------------------------------------------- /handlerUtils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "errors" 8 | "strconv" 9 | "time" 10 | 11 | "code.google.com/p/go-uuid/uuid" 12 | "code.google.com/p/go.crypto/bcrypt" 13 | "github.com/jinzhu/gorm" 14 | ) 15 | 16 | //Authenticates the user by checking username/password against the DB values 17 | func AuthenticateUser(username string, password string, db *gorm.DB) error { 18 | var user User 19 | 20 | dbErr := db.Where(&User{UserName: username}).Find(&user).Error 21 | if dbErr != nil { 22 | if dbErr == gorm.RecordNotFound { 23 | WARNING.Println(UnregisteredUser.Error() + " ,username: " + username) 24 | return UnregisteredUser 25 | } else { 26 | ERROR.Println(dbErr.Error()) 27 | return DatabaseError 28 | } 29 | } 30 | 31 | TRACE.Println("Login attempt by userId " + strconv.FormatInt(user.Id, 10)) 32 | passwordCompareErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) 33 | if passwordCompareErr != nil { 34 | WARNING.Println(passwordCompareErr.Error() + ", userId: " + strconv.FormatInt(user.Id, 10)) 35 | return IncorrectPassword 36 | } 37 | 38 | var passwordRecord PasswordRecord 39 | 40 | dbPasswordRecordErr := db.Where(&PasswordRecord{UserId: user.Id}).Find(&passwordRecord).Error 41 | if dbPasswordRecordErr != nil { 42 | ERROR.Println(dbPasswordRecordErr.Error() + ", userId: " + strconv.FormatInt(user.Id, 10)) 43 | return DatabaseError 44 | } 45 | 46 | if dbPasswordRecordErr == nil && passwordRecord.LoginCount == 0 { 47 | TRACE.Println(FirstLoginPasswordChange.Error() + ", userId: " + strconv.FormatInt(user.Id, 10)) 48 | return FirstLoginPasswordChange 49 | } 50 | 51 | TRACE.Println("User authenticated, userId: " + strconv.FormatInt(user.Id, 10)) 52 | return nil 53 | } 54 | 55 | //Create new token entry in the database 56 | func CreateNewTokenDbEntry(login LoginCredential, db *gorm.DB) (string, error) { 57 | user := User{} 58 | db.Where(&User{UserName: login.Username}).Find(&user) 59 | 60 | token := Token{} 61 | token.UserId = user.Id 62 | token.Key = uuid.New() 63 | token.CreatedAt = time.Now().UTC() 64 | token.LastAccessedAt = time.Now().UTC() 65 | token.Token = generateSessionToken(login.Username+token.CreatedAt.String(), token.Key) 66 | token.Active = true 67 | expiryTime, err := GetTimeOutValue(login.DeviceId) 68 | if err != nil { 69 | ERROR.Println(err.Error()) 70 | return "", err 71 | } 72 | 73 | token.ExpiresAt = expiryTime 74 | 75 | var device DeviceType 76 | db.Where(&DeviceType{DeviceCode: login.DeviceId}).Find(&device) 77 | 78 | token.DeviceTypeId = device.Id 79 | 80 | db.Save(&token) 81 | 82 | db.Save(&ActivityLog{UserId: token.UserId, TokenId: token.Id, ActivityTime: time.Now().UTC(), Event: LOGIN}) 83 | 84 | return token.Token, nil 85 | } 86 | 87 | //Reads app property to get time out value based on the device ID 88 | func GetTimeOutValue(deviceId int) (time.Time, error) { 89 | 90 | var propName string 91 | 92 | if deviceId == web { 93 | propName = "WebTimeOut" 94 | } else if deviceId == mobile { 95 | propName = "MobileTimeOut" 96 | } else { 97 | ERROR.Println("Invalid deviceId") 98 | return time.Time{}, errors.New("Invalid device") 99 | } 100 | 101 | timeOutString, err := GetAppProperties(propName) 102 | 103 | if err != nil { 104 | ERROR.Println(err.Error()) 105 | return time.Time{}, err 106 | } 107 | 108 | timeOutValue, err := strconv.Atoi(timeOutString) 109 | 110 | if err != nil { 111 | ERROR.Println("String to integer conversion failed for " + propName) 112 | return time.Time{}, err 113 | } 114 | 115 | expiryTime := time.Now().UTC().Add(time.Duration(timeOutValue) * time.Minute) 116 | 117 | return expiryTime, nil 118 | } 119 | 120 | //Generates Session Token based on string and key provided 121 | func generateSessionToken(message string, key string) string { 122 | keyBytes := []byte(key) 123 | h := hmac.New(sha256.New, keyBytes) 124 | h.Write([]byte(message)) 125 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 126 | } 127 | 128 | func CreateNewUser(request CreateUserRequest, db *gorm.DB) (int, error) { 129 | 130 | var err error 131 | 132 | if ValidateUniqueUsername(request.Username, db) { 133 | 134 | var user User 135 | 136 | user.UserName = request.Username 137 | user.LegalName = request.LegalName 138 | user.Password, err = EncryptPassword(request.Password) 139 | user.UpdatedAt = time.Now().UTC() 140 | user.Active = false 141 | 142 | if err != nil { 143 | ERROR.Println(err.Error()) 144 | return 500, EncryptionError 145 | } 146 | 147 | db.Save(&user) 148 | TRACE.Println("New user created, userId: " + strconv.FormatInt(user.Id, 10)) 149 | 150 | db.Save(&PasswordRecord{UserId: user.Id, LoginCount: 0}) 151 | TRACE.Println("New password record created, userId: " + strconv.FormatInt(user.Id, 10)) 152 | 153 | return 200, nil 154 | } 155 | 156 | ERROR.Println("Duplicate username " + request.Username + " server validation in create user") 157 | return 409, DuplicateUsernameError 158 | } 159 | 160 | //Returns true if the username is valid i.e. doesn't already exist, else returns false. 161 | func ValidateUniqueUsername(username string, db *gorm.DB) bool { 162 | 163 | TRACE.Println("Validating username " + username) 164 | err := db.Where(&User{UserName: username}).First(&User{}).Error 165 | 166 | if err == gorm.RecordNotFound { 167 | return true 168 | } 169 | WARNING.Println("Username " + username + " already present") 170 | return false 171 | } 172 | 173 | func EncryptPassword(password string) (string, error) { 174 | 175 | encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 176 | 177 | if err != nil { 178 | return "", err 179 | } 180 | 181 | return string(encryptedPasswordBytes), nil 182 | } 183 | 184 | func Status() string { 185 | return "alive at " + time.Now().String() 186 | } 187 | 188 | func ValidateSessionToken(sessionToken string, db *gorm.DB) (error, int64) { 189 | var token Token 190 | 191 | dbErr := db.Where(&Token{Token: sessionToken}).Find(&token).Error 192 | if dbErr != nil { 193 | if dbErr == gorm.RecordNotFound { 194 | WARNING.Println(InvalidSessionToken.Error() + ", sessionToken: " + sessionToken) 195 | return InvalidSessionToken, -1 196 | } 197 | ERROR.Println(dbErr.Error() + ", sessionToken: " + sessionToken) 198 | return dbErr, -1 199 | } 200 | 201 | if token.Active && token.ExpiresAt.After(time.Now().UTC()) { 202 | TRACE.Println("SessionToken validated: " + sessionToken) 203 | timeLeft := token.ExpiresAt.Sub(time.Now().UTC()) 204 | if timeLeft.Minutes() < 5 { 205 | token.ExpiresAt = token.ExpiresAt.Add(time.Duration(GetTimeExtension()) * time.Minute) 206 | } 207 | token.LastAccessedAt = time.Now().UTC() 208 | db.Save(&token) 209 | return nil, token.UserId 210 | } 211 | 212 | return ExpiredSessionToken, -1 213 | } 214 | 215 | func GetTimeExtension() int { 216 | 217 | timeExtensionString, err := GetAppProperties("TimeExtension") 218 | if err != nil { 219 | ERROR.Println(err.Error()) 220 | return 0 221 | } 222 | 223 | extension, conversionErr := strconv.Atoi(timeExtensionString) 224 | if conversionErr != nil { 225 | ERROR.Println(conversionErr.Error()) 226 | return 0 227 | } 228 | 229 | return extension 230 | } 231 | 232 | func ChangePassword(username string, newpassword string, db *gorm.DB) (int, error) { 233 | var user User 234 | var err error 235 | 236 | dbErr := db.Where(&User{UserName: username}).Find(&user).Error 237 | if dbErr != nil { 238 | if dbErr == gorm.RecordNotFound { 239 | WARNING.Println(UnregisteredUser.Error() + " ,username: " + username) 240 | return 401, UnregisteredUser 241 | } else { 242 | ERROR.Println(dbErr.Error()) 243 | return 500, DatabaseError 244 | } 245 | } 246 | 247 | user.Password, err = EncryptPassword(newpassword) 248 | if err != nil { 249 | ERROR.Println(err.Error()) 250 | return 500, EncryptionError 251 | } 252 | 253 | db.Save(&user) 254 | updatePasswordErr := UpdatePasswordRecordLoginCount(user.Id, false, db) 255 | 256 | if updatePasswordErr != nil { 257 | ERROR.Println("Password record not updated for userId: " + strconv.FormatInt(user.Id, 10) + ", Error details: " + updatePasswordErr.Error()) 258 | return 500, DatabaseError 259 | } 260 | 261 | db.Save(&ActivityLog{UserId: user.Id, TokenId: -1, ActivityTime: time.Now().UTC(), Event: PASSWORD_CHANGE}) 262 | TRACE.Println("Password changed for userId: " + strconv.FormatInt(user.Id, 10)) 263 | 264 | return 200, nil 265 | } 266 | 267 | //UpdatePasswordRecordLoginCount if resetFlag = true, LoginCount it reset to 0 else it is incremented by 1 268 | func UpdatePasswordRecordLoginCount(userid int64, resetFlag bool, db *gorm.DB) error { 269 | var record PasswordRecord 270 | dbErr := db.Where(&PasswordRecord{UserId: userid}).Find(&record).Error 271 | if dbErr != nil { 272 | return dbErr 273 | } 274 | 275 | if resetFlag { 276 | record.LoginCount = 0 277 | } else { 278 | record.LoginCount++ 279 | } 280 | 281 | db.Save(&record) 282 | 283 | return nil 284 | } 285 | 286 | //CheckPermissions for user 287 | //TODO: Refactor with raw sql if performance bottleneck 288 | func CheckPermissionsForUser(userid int64, permission Permission, db *gorm.DB) error { 289 | if userid == int64(-1) { 290 | return PermissionDenied 291 | } 292 | 293 | err := db.Where(&Permission{PermissionDescription: permission.PermissionDescription}).Find(&permission).Error 294 | if err != nil { 295 | ERROR.Println(err.Error() + " while checking permission for: " + permission.PermissionDescription) 296 | return err 297 | } 298 | 299 | var groupPermission GroupPermission 300 | err = db.Where(&GroupPermission{PermissionId: permission.Id}).Find(&groupPermission).Error 301 | if err != nil { 302 | ERROR.Println(err.Error() + " while finding groupPermission for: " + strconv.FormatInt(permission.Id, 10)) 303 | return err 304 | } 305 | 306 | var userGroup UserGroup 307 | err = db.Where(&UserGroup{UserId: userid, GroupId: groupPermission.GroupId}).Find(&userGroup).Error 308 | if err == gorm.RecordNotFound { 309 | WARNING.Println(PermissionDenied.Error() + " for user: " + strconv.FormatInt(userid, 10) + 310 | " for Permission: " + permission.PermissionDescription) 311 | return PermissionDenied 312 | } else if err != nil { 313 | ERROR.Println(err.Error() + " in UserGroup while searching for userId: " + strconv.FormatInt(userid, 10) + 314 | " and groupId: " + strconv.FormatInt(groupPermission.GroupId, 10)) 315 | return err 316 | } 317 | 318 | TRACE.Println("Permission verified for userId: " + strconv.FormatInt(userid, 10) + " , permission: " + permission.PermissionDescription) 319 | return nil 320 | } 321 | 322 | //End session for logout 323 | -------------------------------------------------------------------------------- /handlerUtils_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "code.google.com/p/go.crypto/bcrypt" 8 | "github.com/jinzhu/gorm" 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | func TestHandlerUtils(t *testing.T) { 13 | TestingT(t) 14 | } 15 | 16 | type HandlerUtilsTestSuite struct{} 17 | 18 | var _ = Suite(&HandlerUtilsTestSuite{}) 19 | 20 | func (suite *HandlerUtilsTestSuite) SetUpSuite(c *C) { 21 | initDbFlag := false 22 | InitApp(testLogFile, &initDbFlag) 23 | TRACE.Println("HandlerUtilsTestSuite Setup") 24 | } 25 | 26 | func (suite *HandlerUtilsTestSuite) TearDownSuite(c *C) { 27 | TRACE.Println("HandlerUtilsTestSuite TearDown") 28 | defer cleanUpAfterShutdown() 29 | } 30 | 31 | func (suite *HandlerUtilsTestSuite) TestGenerateSessionToken(c *C) { 32 | TRACE.Println("Running test: TestGenerateSessionToken") 33 | 34 | expectedSessionToken := "8N7PlLvnGgnE2gFU7+AkSxmAc02cXFkOLlFD5gTuOjo=" 35 | 36 | actualSessionToken := generateSessionToken("testMessage", "testKey") 37 | 38 | c.Assert(expectedSessionToken, Equals, actualSessionToken) 39 | } 40 | 41 | func (suite *HandlerUtilsTestSuite) TestCreateNewUserWithUniqueUsername(c *C) { 42 | TRACE.Println("Running test: TestCreateNewUserWithUniqueUsername") 43 | 44 | tx := dbConnection.Begin() 45 | 46 | testString := "UniqueTestUser123321" 47 | 48 | var testCreateUserRequest CreateUserRequest 49 | testCreateUserRequest.Username = testString 50 | testCreateUserRequest.LegalName = testString 51 | testCreateUserRequest.Password = testString 52 | 53 | status, err := CreateNewUser(testCreateUserRequest, tx) 54 | 55 | c.Check(err, IsNil) 56 | c.Assert(status, Equals, 200) 57 | 58 | var user User 59 | 60 | dbErr := tx.Where(&User{UserName: testString}).First(&user).Error 61 | c.Assert(dbErr, IsNil) 62 | comparePasswordErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(testString)) 63 | c.Assert(comparePasswordErr, IsNil) 64 | 65 | var passwordRecord PasswordRecord 66 | tx.Where(&PasswordRecord{UserId: user.Id}).Find(&passwordRecord) 67 | c.Assert(passwordRecord.LoginCount, Equals, 0) 68 | 69 | tx.Rollback() 70 | } 71 | 72 | func (suite *HandlerUtilsTestSuite) TestCreateNewUserWithDuplicateUsername(c *C) { 73 | TRACE.Println("Running test: TestCreateNewUserWithDuplicateUsername") 74 | 75 | tx := dbConnection.Begin() 76 | 77 | testString := "UniqueTestUser123321" 78 | 79 | var testCreateUserRequest CreateUserRequest 80 | testCreateUserRequest.Username = testString 81 | testCreateUserRequest.LegalName = testString 82 | testCreateUserRequest.Password = testString 83 | 84 | status, err := CreateNewUser(testCreateUserRequest, tx) 85 | 86 | c.Check(err, IsNil) 87 | c.Assert(status, Equals, 200) 88 | 89 | dbErr := tx.Where(&User{UserName: testString}).First(&User{}).Error 90 | 91 | c.Assert(dbErr, IsNil) 92 | 93 | statusDuplicateEntry, err := CreateNewUser(testCreateUserRequest, tx) 94 | 95 | c.Check(statusDuplicateEntry, Equals, 409) 96 | c.Assert(err, NotNil) 97 | 98 | tx.Rollback() 99 | } 100 | 101 | func (suite *HandlerUtilsTestSuite) TestCreateNewTokenDbEntry(c *C) { 102 | TRACE.Println("Running test: TestCreateNewTokenDbEntry") 103 | 104 | tx := dbConnection.Begin() 105 | 106 | testString := "UniqueTestUser123321" 107 | 108 | var testCreateUserRequest = CreateUserRequest{Username: testString, 109 | LegalName: testString, Password: testString} 110 | 111 | status, err := CreateNewUser(testCreateUserRequest, tx) 112 | 113 | c.Check(err, IsNil) 114 | c.Assert(status, Equals, 200) 115 | 116 | var user User 117 | dbErr := tx.Where(&User{UserName: testString}).Find(&user).Error 118 | c.Assert(dbErr, IsNil) 119 | 120 | var testLoginCredential = LoginCredential{Username: testString, Password: testString, 121 | DeviceId: web} 122 | 123 | sessionToken, err := CreateNewTokenDbEntry(testLoginCredential, tx) 124 | 125 | var token Token 126 | dbErr = tx.Where(&Token{Token: sessionToken}).Find(&token).Error 127 | c.Assert(dbErr, IsNil) 128 | 129 | var activityLog ActivityLog 130 | dbErr = tx.Where(&ActivityLog{UserId: user.Id, TokenId: token.Id}).Find(&activityLog).Error 131 | c.Assert(dbErr, IsNil) 132 | 133 | tx.Rollback() 134 | } 135 | 136 | func (suite *HandlerUtilsTestSuite) TestGetTimeOutValue(c *C) { 137 | 138 | var err error 139 | 140 | _, err = GetTimeOutValue(web) 141 | c.Assert(err, IsNil) 142 | 143 | _, err = GetTimeOutValue(mobile) 144 | c.Assert(err, IsNil) 145 | 146 | _, err = GetTimeOutValue(-100) 147 | c.Assert(err.Error(), Equals, "Invalid device") 148 | } 149 | 150 | func (suite *HandlerUtilsTestSuite) TestAuthenticateUser(c *C) { 151 | TRACE.Println("Running test: TestAuthenticateUser") 152 | tx := dbConnection.Begin() 153 | 154 | testString := "UniqueTestUser123321" 155 | 156 | testCreateUserRequest := CreateUserRequest{Username: testString, LegalName: testString, Password: testString} 157 | status, err := CreateNewUser(testCreateUserRequest, tx) 158 | 159 | c.Assert(status, Equals, 200) 160 | c.Assert(err, IsNil) 161 | 162 | validLoginCredential := LoginCredential{Username: testString, Password: testString, DeviceId: mobile} 163 | invalidLoginCredentialWrongPassword := LoginCredential{Username: testString, Password: "invalid", DeviceId: mobile} 164 | invalidLoginCredentialWrongUsername := LoginCredential{Username: "invalid", Password: testString, DeviceId: mobile} 165 | 166 | c.Assert(AuthenticateUser(validLoginCredential.Username, validLoginCredential.Password, tx), Equals, FirstLoginPasswordChange) 167 | c.Assert(AuthenticateUser(invalidLoginCredentialWrongPassword.Username, invalidLoginCredentialWrongPassword.Password, tx), Equals, IncorrectPassword) 168 | c.Assert(AuthenticateUser(invalidLoginCredentialWrongUsername.Username, invalidLoginCredentialWrongUsername.Password, tx), Equals, UnregisteredUser) 169 | 170 | newPassword := "newPassword" 171 | changePasswordStatus, chagePasswordErr := ChangePassword(validLoginCredential.Username, newPassword, tx) 172 | c.Assert(changePasswordStatus, Equals, 200) 173 | c.Assert(chagePasswordErr, IsNil) 174 | 175 | newValidLoginCredential := LoginCredential{Username: testString, Password: newPassword, DeviceId: mobile} 176 | c.Assert(AuthenticateUser(newValidLoginCredential.Username, newValidLoginCredential.Password, tx), Equals, nil) 177 | 178 | tx.Rollback() 179 | } 180 | 181 | func (suite *HandlerUtilsTestSuite) TestGetTimeExtension(c *C) { 182 | TRACE.Println("Running test: TestGetTimeExtension") 183 | var timeExtensionFromDb AppProperties 184 | 185 | dbConnection.Where(&AppProperties{PropertyName: "TimeExtension"}).Find(&timeExtensionFromDb) 186 | 187 | timeExtensionFromMethod := GetTimeExtension() 188 | 189 | extension, conversionErr := strconv.Atoi(timeExtensionFromDb.PropertyValue) 190 | 191 | c.Check(conversionErr, IsNil) 192 | c.Assert(extension, Equals, timeExtensionFromMethod) 193 | } 194 | 195 | func (suite *HandlerUtilsTestSuite) TestChangePassword(c *C) { 196 | TRACE.Println("Running test: TestChangePassword") 197 | tx := dbConnection.Begin() 198 | 199 | testString := "UniqueTestUser123321" 200 | 201 | testCreateUserRequest := CreateUserRequest{Username: testString, LegalName: testString, Password: testString} 202 | status, err := CreateNewUser(testCreateUserRequest, tx) 203 | 204 | c.Assert(err, IsNil) 205 | c.Assert(status, Equals, 200) 206 | 207 | var testUser User 208 | 209 | tx.Where(&User{UserName: testString}).Find(&testUser) 210 | 211 | compareErr := bcrypt.CompareHashAndPassword([]byte(testUser.Password), []byte(testString)) 212 | c.Assert(compareErr, IsNil) 213 | 214 | newPassword := "newPassword" 215 | changePasswordStatus, changePasswordErr := ChangePassword(testString, newPassword, tx) 216 | c.Assert(changePasswordErr, IsNil) 217 | c.Assert(changePasswordStatus, Equals, 200) 218 | 219 | tx.Where(&User{UserName: testString}).Find(&testUser) 220 | 221 | compareErr = bcrypt.CompareHashAndPassword([]byte(testUser.Password), []byte(newPassword)) 222 | c.Assert(compareErr, IsNil) 223 | 224 | changePasswordStatusUnregisteredUser, changePasswordErrUnregisteredUser := ChangePassword("unregisteredUser123321", newPassword, tx) 225 | c.Assert(changePasswordErrUnregisteredUser, Equals, UnregisteredUser) 226 | c.Assert(changePasswordStatusUnregisteredUser, Equals, 401) 227 | 228 | tx.Rollback() 229 | } 230 | 231 | func (suite *HandlerUtilsTestSuite) TestValidateSessionToken(c *C) { 232 | TRACE.Println("Running test: TestValidateSessionToken") 233 | 234 | tx := dbConnection.Begin() 235 | 236 | testString := "UniqueTestUser123321" 237 | 238 | var testCreateUserRequest = CreateUserRequest{Username: testString, 239 | LegalName: testString, Password: testString} 240 | 241 | status, err := CreateNewUser(testCreateUserRequest, tx) 242 | 243 | c.Check(err, IsNil) 244 | c.Assert(status, Equals, 200) 245 | 246 | var user User 247 | dbErr := tx.Where(&User{UserName: testString}).Find(&user).Error 248 | c.Assert(dbErr, IsNil) 249 | 250 | newPassword := "newPassword" 251 | changePasswordStatus, chagePasswordErr := ChangePassword(user.UserName, newPassword, tx) 252 | c.Assert(changePasswordStatus, Equals, 200) 253 | c.Assert(chagePasswordErr, IsNil) 254 | 255 | var testLoginCredential = LoginCredential{Username: testString, Password: newPassword, 256 | DeviceId: web} 257 | 258 | sessionToken, err := CreateNewTokenDbEntry(testLoginCredential, tx) 259 | c.Assert(err, IsNil) 260 | 261 | var token Token 262 | dbErr = tx.Where(&Token{Token: sessionToken}).Find(&token).Error 263 | c.Assert(dbErr, IsNil) 264 | 265 | var activityLog ActivityLog 266 | dbErr = tx.Where(&ActivityLog{UserId: user.Id, TokenId: token.Id}).Find(&activityLog).Error 267 | c.Assert(dbErr, IsNil) 268 | 269 | err, userId := ValidateSessionToken("testSessionToken", tx) 270 | c.Assert(err, Equals, InvalidSessionToken) 271 | c.Assert(userId, Equals, int64(-1)) 272 | 273 | err, userId = ValidateSessionToken(sessionToken, tx) 274 | c.Assert(err, IsNil) 275 | c.Assert(userId, Equals, token.UserId) 276 | 277 | token.Active = false 278 | tx.Save(&token) 279 | 280 | err, userId = ValidateSessionToken(sessionToken, tx) 281 | c.Assert(err, Equals, ExpiredSessionToken) 282 | c.Assert(userId, Equals, int64(-1)) 283 | 284 | tx.Rollback() 285 | 286 | } 287 | 288 | func (suite *HandlerUtilsTestSuite) TestCheckPermissions(c *C) { 289 | TRACE.Println("Running test :TestCheckPermissions") 290 | 291 | tx := dbConnection.Begin() 292 | 293 | validPermission := Permission{PermissionDescription: "VALID_PERMISSION"} 294 | invalidPermission := Permission{PermissionDescription: "INVALID_PERMISSION"} 295 | 296 | validGroup := Group{GroupDescription: "VALID_GROUP"} 297 | invalidGroup := Group{GroupDescription: "INVALID_GROUP"} 298 | 299 | user := User{UserName: "User128256", LegalName: "User128256", Password: "Password128256", Active: true} 300 | tx.Save(&user) 301 | c.Assert(user.Id, NotNil) 302 | 303 | tx.Save(&validPermission) 304 | tx.Save(&invalidPermission) 305 | 306 | tx.Save(&validGroup) 307 | tx.Save(&invalidGroup) 308 | 309 | groupPermission := GroupPermission{GroupId: validGroup.Id, PermissionId: validPermission.Id} 310 | tx.Save(&groupPermission) 311 | 312 | userGroup := UserGroup{UserId: user.Id, GroupId: validGroup.Id} 313 | tx.Save(&userGroup) 314 | 315 | err := CheckPermissionsForUser(int64(-1), validPermission, tx) 316 | c.Assert(err, Equals, PermissionDenied) 317 | 318 | err = CheckPermissionsForUser(user.Id, invalidPermission, tx) 319 | c.Assert(err, Equals, gorm.RecordNotFound) 320 | 321 | absentPermission := Permission{PermissionDescription: "NOT_PRESENT"} 322 | err = CheckPermissionsForUser(user.Id, absentPermission, tx) 323 | c.Assert(err, Equals, gorm.RecordNotFound) 324 | 325 | invalidGroupPermission := GroupPermission{GroupId: invalidGroup.Id, PermissionId: invalidPermission.Id} 326 | tx.Save(&invalidGroupPermission) 327 | 328 | err = CheckPermissionsForUser(user.Id, invalidPermission, tx) 329 | c.Assert(err, Equals, PermissionDenied) 330 | 331 | err = CheckPermissionsForUser(user.Id, validPermission, tx) 332 | c.Assert(err, IsNil) 333 | 334 | tx.Rollback() 335 | } 336 | -------------------------------------------------------------------------------- /coverage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Coverage Report 5 | 6 | 127 | 128 | 129 | 130 |
Coverage Report
131 |
Generated on 28 Nov 14 00:46 -0700 with gocov-html
Package Overview: gondalf 58.61%

This is a coverage report created after analysis of the gondalf package. It 132 | has been generated with the following command:

gocov test gondalf | gocov-html

Here are the stats. Please select a function name to view its implementation and see what's left for testing.

133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
ValidateUniqueUsername(...)gondalf/handlerUtils.go100.00%6/6
InitLogger(...)gondalf/util.go100.00%5/5
generateSessionToken(...)gondalf/handlerUtils.go100.00%4/4
GetAppProperties(...)gondalf/util.go100.00%4/4
cleanUpAfterShutdown(...)gondalf/server.go100.00%3/3
LoadAppPropertiesFromDb(...)gondalf/util.go100.00%2/2
StartScheduledJobs(...)gondalf/scheduledJobs.go100.00%2/2
GetDBConnection(...)gondalf/db.go94.74%18/19
InsertAppProperties(...)gondalf/db.go91.67%11/12
CheckPermissionsForUser(...)gondalf/handlerUtils.go90.48%19/21
CreateNewTokenDbEntry(...)gondalf/handlerUtils.go90.00%18/20
CreateNewUser(...)gondalf/handlerUtils.go88.89%16/18
InsertDeviceTypes(...)gondalf/db.go87.50%7/8
JobRefreshAppProperties(...)gondalf/scheduledJobs.go85.71%6/7
LoadConfigurationFromFile(...)gondalf/util.go83.33%5/6
AuthenticateUser(...)gondalf/handlerUtils.go82.61%19/23
ValidateSessionToken(...)gondalf/handlerUtils.go82.35%14/17
UpdatePasswordRecordLoginCount(...)gondalf/handlerUtils.go77.78%7/9
GetTimeOutValue(...)gondalf/handlerUtils.go76.47%13/17
EncryptPassword(...)gondalf/handlerUtils.go75.00%3/4
ChangePassword(...)gondalf/handlerUtils.go71.43%15/21
JobArchiveExpiredSessionToken(...)gondalf/scheduledJobs.go70.00%7/10
InitApp(...)gondalf/util.go62.50%15/24
GetTimeExtension(...)gondalf/handlerUtils.go55.56%5/9
@52:5(...)gondalf/scheduledJobs.go50.00%2/4
@24:5(...)gondalf/scheduledJobs.go40.00%2/5
ChangePasswordHandler(...)gondalf/handlers.go0.00%0/19
main(...)gondalf/server.go0.00%0/18
InitDB(...)gondalf/db.go0.00%0/13
LoginHandler(...)gondalf/handlers.go0.00%0/13
ValidateSessionTokenHandler(...)gondalf/handlers.go0.00%0/12
ArchiveTokenAfterCutOffTime(...)gondalf/util.go0.00%0/12
CreateUserHandler(...)gondalf/handlers.go0.00%0/10
CheckPermissionsForUserHandler(...)gondalf/handlers.go0.00%0/8
ValidateUsernameHandler(...)gondalf/handlers.go0.00%0/2
StatusHandler(...)gondalf/handlers.go0.00%0/1
Status(...)gondalf/handlerUtils.go0.00%0/1
gondalf58.61%228/389
172 | 173 |
func ValidateUniqueUsername
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

174 |
161
func ValidateUniqueUsername(username string, db *gorm.DB) bool {
162
163
        TRACE.Println("Validating username " + username)
164
        err := db.Where(&User{UserName: username}).First(&User{}).Error
165
166
        if err == gorm.RecordNotFound {
167
                return true
168
        }
169
        WARNING.Println("Username " + username + " already present")
170
        return false
171
}
175 | 176 |
func InitLogger
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:

177 |
101
func InitLogger(traceHandle io.Writer, infoHandle io.Writer, warningHandle io.Writer, errorHandle io.Writer, databaseHandle io.Writer) {
102
103
        TRACE = log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile)
104
105
        INFO = log.New(infoHandle, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
106
107
        WARNING = log.New(warningHandle, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
108
109
        ERROR = log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
110
111
        DATABASE = log.New(databaseHandle, "DATABASE: ", log.Ldate|log.Ltime|log.Lshortfile)
112
}
178 | 179 |
func generateSessionToken
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

180 |
121
func generateSessionToken(message string, key string) string {
122
        keyBytes := []byte(key)
123
        h := hmac.New(sha256.New, keyBytes)
124
        h.Write([]byte(message))
125
        return base64.StdEncoding.EncodeToString(h.Sum(nil))
126
}
181 | 182 |
func GetAppProperties
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:

183 |
114
func GetAppProperties(propertyName string) (string, error) {
115
        for index := range properties {
116
                if properties[index].PropertyName == propertyName {
117
                        return properties[index].PropertyValue, nil
118
                }
119
        }
120
        return "", errors.New("App property " + propertyName + " not set")
121
}
184 | 185 |
func cleanUpAfterShutdown
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/server.go:

186 |
69
func cleanUpAfterShutdown() {
70
        //TRACE.Println("Sending kill signal to all routines by closing quit channel")
71
        //close(quit)
72
73
        TRACE.Println("Cleaning up dbConnection and file stream")
74
        dbConnection.Close()
75
        file.Close()
76
}
187 | 188 |
func LoadAppPropertiesFromDb
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:

189 |
135
func LoadAppPropertiesFromDb() {
136
        dbConnection.Find(&properties)
137
        TRACE.Println("App properties array initialized at: " + time.Now().String())
138
}
190 | 191 |
func StartScheduledJobs
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:

192 |
9
func StartScheduledJobs() {
10
        JobRefreshAppProperties()
11
        JobArchiveExpiredSessionToken()
12
}
193 | 194 |
func GetDBConnection
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:

195 |
74
func GetDBConnection() gorm.DB {
75
76
        LoadConfigurationFromFile()
77
78
        var connParam ConnectionParameters
79
80
        connParam.username = viper.GetString("dbUsername")
81
        connParam.password = viper.GetString("dbPassword")
82
        connParam.host = viper.GetString("dbHost")
83
        connParam.port = viper.GetString("dbPort")
84
        connParam.dbname = viper.GetString("dbName")
85
        connParam.sslmode = viper.GetString("dbSSLmode")
86
87
        source := "user=" + connParam.username +
88
                " password=" + connParam.password +
89
                " dbname=" + connParam.dbname +
90
                " port=" + connParam.port +
91
                " host=" + connParam.host +
92
                " sslmode=" + connParam.sslmode
93
94
        db, err := gorm.Open("postgres", source)
95
96
        if err != nil {
97
                ERROR.Panicln("Error opening DB connection")
98
        }
99
100
        TRACE.Println("DB Connection opened")
101
102
        maxIdleConnections, _ := strconv.Atoi(viper.GetString("dbMaxIdleConnections"))
103
        maxOpenConnections, _ := strconv.Atoi(viper.GetString("dbMaxOpenConnections"))
104
105
        db.DB().SetMaxIdleConns(maxIdleConnections)
106
        db.DB().SetMaxOpenConns(maxOpenConnections)
107
        db.DB().Ping()
108
109
        return db
110
}
196 | 197 |
func InsertAppProperties
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:

198 |
36
func InsertAppProperties(db *gorm.DB) bool {
37
38
        webTimeOut := AppProperties{PropertyName: "WebTimeOut", PropertyValue: "30", UpdatedAt: time.Now().UTC()}
39
        mobileTimeOut := AppProperties{PropertyName: "MobileTimeOut", PropertyValue: "720", UpdatedAt: time.Now().UTC()}
40
        dbDebugLogs := AppProperties{PropertyName: "DbDebugLogs", PropertyValue: "false", UpdatedAt: time.Now().UTC()}
41
        timeExtension := AppProperties{PropertyName: "TimeExtension", PropertyValue: "5", UpdatedAt: time.Now().UTC()}
42
        tokenCutOffTime := AppProperties{PropertyName: "TokenCutOffTime", PropertyValue: "30", UpdatedAt: time.Now().UTC()}
43
        tokenCleanUpFrequency := AppProperties{PropertyName: "TokenCleanUpFrequency", PropertyValue: "180", UpdatedAt: time.Now().UTC()}
44
45
        propertiesSlice := []AppProperties{webTimeOut, mobileTimeOut, dbDebugLogs, timeExtension, tokenCutOffTime, tokenCleanUpFrequency}
46
47
        for i := range propertiesSlice {
48
                existsErr := db.Where(&AppProperties{PropertyName: propertiesSlice[i].PropertyName}).Find(&AppProperties{}).Error
49
                if existsErr == gorm.RecordNotFound {
50
                        db.Save(&propertiesSlice[i])
51
                }
52
        }
53
54
        return true
55
}
199 | 200 |
func CheckPermissionsForUser
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

201 |
288
func CheckPermissionsForUser(userid int64, permission Permission, db *gorm.DB) error {
289
        if userid == int64(-1) {
290
                return PermissionDenied
291
        }
292
293
        err := db.Where(&Permission{PermissionDescription: permission.PermissionDescription}).Find(&permission).Error
294
        if err != nil {
295
                ERROR.Println(err.Error() + " while checking permission for: " + permission.PermissionDescription)
296
                return err
297
        }
298
299
        var groupPermission GroupPermission
300
        err = db.Where(&GroupPermission{PermissionId: permission.Id}).Find(&groupPermission).Error
301
        if err != nil {
302
                ERROR.Println(err.Error() + " while finding groupPermission for: " + strconv.FormatInt(permission.Id, 10))
303
                return err
304
        }
305
306
        var userGroup UserGroup
307
        err = db.Where(&UserGroup{UserId: userid, GroupId: groupPermission.GroupId}).Find(&userGroup).Error
308
        if err == gorm.RecordNotFound {
309
                WARNING.Println(PermissionDenied.Error() + " for user: " + strconv.FormatInt(userid, 10) +
310
                        " for Permission: " + permission.PermissionDescription)
311
                return PermissionDenied
312
        } else if err != nil {
313
                ERROR.Println(err.Error() + " in UserGroup while searching for userId: " + strconv.FormatInt(userid, 10) +
314
                        " and groupId: " + strconv.FormatInt(groupPermission.GroupId, 10))
315
                return err
316
        }
317
318
        TRACE.Println("Permission verified for userId: " + strconv.FormatInt(userid, 10) + " , permission: " + permission.PermissionDescription)
319
        return nil
320
}
202 | 203 |
func CreateNewTokenDbEntry
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

204 |
56
func CreateNewTokenDbEntry(login LoginCredential, db *gorm.DB) (string, error) {
57
        user := User{}
58
        db.Where(&User{UserName: login.Username}).Find(&user)
59
60
        token := Token{}
61
        token.UserId = user.Id
62
        token.Key = uuid.New()
63
        token.CreatedAt = time.Now().UTC()
64
        token.LastAccessedAt = time.Now().UTC()
65
        token.Token = generateSessionToken(login.Username+token.CreatedAt.String(), token.Key)
66
        token.Active = true
67
        expiryTime, err := GetTimeOutValue(login.DeviceId)
68
        if err != nil {
69
                ERROR.Println(err.Error())
70
                return "", err
71
        }
72
73
        token.ExpiresAt = expiryTime
74
75
        var device DeviceType
76
        db.Where(&DeviceType{DeviceCode: login.DeviceId}).Find(&device)
77
78
        token.DeviceTypeId = device.Id
79
80
        db.Save(&token)
81
82
        db.Save(&ActivityLog{UserId: token.UserId, TokenId: token.Id, ActivityTime: time.Now().UTC(), Event: LOGIN})
83
84
        return token.Token, nil
85
}
205 | 206 |
func CreateNewUser
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

207 |
128
func CreateNewUser(request CreateUserRequest, db *gorm.DB) (int, error) {
129
130
        var err error
131
132
        if ValidateUniqueUsername(request.Username, db) {
133
134
                var user User
135
136
                user.UserName = request.Username
137
                user.LegalName = request.LegalName
138
                user.Password, err = EncryptPassword(request.Password)
139
                user.UpdatedAt = time.Now().UTC()
140
                user.Active = false
141
142
                if err != nil {
143
                        ERROR.Println(err.Error())
144
                        return 500, EncryptionError
145
                }
146
147
                db.Save(&user)
148
                TRACE.Println("New user created, userId: " + strconv.FormatInt(user.Id, 10))
149
150
                db.Save(&PasswordRecord{UserId: user.Id, LoginCount: 0})
151
                TRACE.Println("New password record created, userId: " + strconv.FormatInt(user.Id, 10))
152
153
                return 200, nil
154
        }
155
156
        ERROR.Println("Duplicate username " + request.Username + " server validation in create user")
157
        return 409, DuplicateUsernameError
158
}
208 | 209 |
func InsertDeviceTypes
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:

210 |
57
func InsertDeviceTypes(db *gorm.DB) bool {
58
        webDevice := DeviceType{Device: "web", DeviceCode: web}
59
        mobileDevice := DeviceType{Device: "mobile", DeviceCode: mobile}
60
61
        devicesSlice := []DeviceType{webDevice, mobileDevice}
62
63
        for i := range devicesSlice {
64
                existsErr := db.Where(&devicesSlice[i]).Find(&DeviceType{}).Error
65
                if existsErr == gorm.RecordNotFound {
66
                        db.Save(&devicesSlice[i])
67
                }
68
        }
69
70
        return true
71
}
211 | 212 |
func JobRefreshAppProperties
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:

213 |
14
func JobRefreshAppProperties() {
15
16
        LoadConfigurationFromFile()
17
        appPropertiesRefreshTimeinMinutes, err := strconv.Atoi(viper.GetString("appPropertiesRefreshTimeinMinutes"))
18
        if err != nil {
19
                ERROR.Panicln("appPropertiesRefreshTimeinMinutes missing from the config file")
20
        }
21
22
        ticker := time.NewTicker(time.Duration(appPropertiesRefreshTimeinMinutes) * time.Minute)
23
        TRACE.Println("Starting goroutine for refreshing app properties")
24
        go func() {
25
                for {
26
                        select {
27
                        case <-ticker.C:
28
                                LoadAppPropertiesFromDb()
29
                        case <-quit:
30
                                TRACE.Println("Quit signal: JobRefreshAppProperties")
31
                                ticker.Stop()
32
                        }
33
                }
34
        }()
35
}
214 | 215 |
func LoadConfigurationFromFile
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:

216 |
123
func LoadConfigurationFromFile() {
124
        TRACE.Println("Loading configuration from " + configName)
125
126
        viper.SetConfigName(configName)
127
        viper.AddConfigPath(".")
128
        err := viper.ReadInConfig()
129
130
        if err != nil {
131
                panic("Configuration couldn't be initialized, panicking now " + err.Error())
132
        }
133
}
217 | 218 |
func AuthenticateUser
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

219 |
17
func AuthenticateUser(username string, password string, db *gorm.DB) error {
18
        var user User
19
20
        dbErr := db.Where(&User{UserName: username}).Find(&user).Error
21
        if dbErr != nil {
22
                if dbErr == gorm.RecordNotFound {
23
                        WARNING.Println(UnregisteredUser.Error() + " ,username: " + username)
24
                        return UnregisteredUser
25
                } else {
26
                        ERROR.Println(dbErr.Error())
27
                        return DatabaseError
28
                }
29
        }
30
31
        TRACE.Println("Login attempt by userId " + strconv.FormatInt(user.Id, 10))
32
        passwordCompareErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
33
        if passwordCompareErr != nil {
34
                WARNING.Println(passwordCompareErr.Error() + ", userId: " + strconv.FormatInt(user.Id, 10))
35
                return IncorrectPassword
36
        }
37
38
        var passwordRecord PasswordRecord
39
40
        dbPasswordRecordErr := db.Where(&PasswordRecord{UserId: user.Id}).Find(&passwordRecord).Error
41
        if dbPasswordRecordErr != nil {
42
                ERROR.Println(dbPasswordRecordErr.Error() + ", userId: " + strconv.FormatInt(user.Id, 10))
43
                return DatabaseError
44
        }
45
46
        if dbPasswordRecordErr == nil && passwordRecord.LoginCount == 0 {
47
                TRACE.Println(FirstLoginPasswordChange.Error() + ", userId: " + strconv.FormatInt(user.Id, 10))
48
                return FirstLoginPasswordChange
49
        }
50
51
        TRACE.Println("User authenticated, userId: " + strconv.FormatInt(user.Id, 10))
52
        return nil
53
}
220 | 221 |
func ValidateSessionToken
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

222 |
188
func ValidateSessionToken(sessionToken string, db *gorm.DB) (error, int64) {
189
        var token Token
190
191
        dbErr := db.Where(&Token{Token: sessionToken}).Find(&token).Error
192
        if dbErr != nil {
193
                if dbErr == gorm.RecordNotFound {
194
                        WARNING.Println(InvalidSessionToken.Error() + ", sessionToken: " + sessionToken)
195
                        return InvalidSessionToken, -1
196
                }
197
                ERROR.Println(dbErr.Error() + ", sessionToken: " + sessionToken)
198
                return dbErr, -1
199
        }
200
201
        if token.Active && token.ExpiresAt.After(time.Now().UTC()) {
202
                TRACE.Println("SessionToken validated: " + sessionToken)
203
                timeLeft := token.ExpiresAt.Sub(time.Now().UTC())
204
                if timeLeft.Minutes() < 5 {
205
                        token.ExpiresAt = token.ExpiresAt.Add(time.Duration(GetTimeExtension()) * time.Minute)
206
                }
207
                token.LastAccessedAt = time.Now().UTC()
208
                db.Save(&token)
209
                return nil, token.UserId
210
        }
211
212
        return ExpiredSessionToken, -1
213
}
223 | 224 |
func UpdatePasswordRecordLoginCount
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

225 |
268
func UpdatePasswordRecordLoginCount(userid int64, resetFlag bool, db *gorm.DB) error {
269
        var record PasswordRecord
270
        dbErr := db.Where(&PasswordRecord{UserId: userid}).Find(&record).Error
271
        if dbErr != nil {
272
                return dbErr
273
        }
274
275
        if resetFlag {
276
                record.LoginCount = 0
277
        } else {
278
                record.LoginCount++
279
        }
280
281
        db.Save(&record)
282
283
        return nil
284
}
226 | 227 |
func GetTimeOutValue
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

228 |
88
func GetTimeOutValue(deviceId int) (time.Time, error) {
89
90
        var propName string
91
92
        if deviceId == web {
93
                propName = "WebTimeOut"
94
        } else if deviceId == mobile {
95
                propName = "MobileTimeOut"
96
        } else {
97
                ERROR.Println("Invalid deviceId")
98
                return time.Time{}, errors.New("Invalid device")
99
        }
100
101
        timeOutString, err := GetAppProperties(propName)
102
103
        if err != nil {
104
                ERROR.Println(err.Error())
105
                return time.Time{}, err
106
        }
107
108
        timeOutValue, err := strconv.Atoi(timeOutString)
109
110
        if err != nil {
111
                ERROR.Println("String to integer conversion failed for " + propName)
112
                return time.Time{}, err
113
        }
114
115
        expiryTime := time.Now().UTC().Add(time.Duration(timeOutValue) * time.Minute)
116
117
        return expiryTime, nil
118
}
229 | 230 |
func EncryptPassword
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

231 |
173
func EncryptPassword(password string) (string, error) {
174
175
        encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
176
177
        if err != nil {
178
                return "", err
179
        }
180
181
        return string(encryptedPasswordBytes), nil
182
}
232 | 233 |
func ChangePassword
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

234 |
232
func ChangePassword(username string, newpassword string, db *gorm.DB) (int, error) {
233
        var user User
234
        var err error
235
236
        dbErr := db.Where(&User{UserName: username}).Find(&user).Error
237
        if dbErr != nil {
238
                if dbErr == gorm.RecordNotFound {
239
                        WARNING.Println(UnregisteredUser.Error() + " ,username: " + username)
240
                        return 401, UnregisteredUser
241
                } else {
242
                        ERROR.Println(dbErr.Error())
243
                        return 500, DatabaseError
244
                }
245
        }
246
247
        user.Password, err = EncryptPassword(newpassword)
248
        if err != nil {
249
                ERROR.Println(err.Error())
250
                return 500, EncryptionError
251
        }
252
253
        db.Save(&user)
254
        updatePasswordErr := UpdatePasswordRecordLoginCount(user.Id, false, db)
255
256
        if updatePasswordErr != nil {
257
                ERROR.Println("Password record not updated for userId: " + strconv.FormatInt(user.Id, 10) + ", Error details: " + updatePasswordErr.Error())
258
                return 500, DatabaseError
259
        }
260
261
        db.Save(&ActivityLog{UserId: user.Id, TokenId: -1, ActivityTime: time.Now().UTC(), Event: PASSWORD_CHANGE})
262
        TRACE.Println("Password changed for userId: " + strconv.FormatInt(user.Id, 10))
263
264
        return 200, nil
265
}
235 | 236 |
func JobArchiveExpiredSessionToken
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:

237 |
37
func JobArchiveExpiredSessionToken() {
38
39
        tokenCleanUpFrequencyString, err := GetAppProperties("TokenCleanUpFrequency")
40
        if err != nil {
41
                ERROR.Println(err.Error())
42
        }
43
44
        tokenCleanUpFrequencyInteger, err := strconv.Atoi(tokenCleanUpFrequencyString)
45
        if err != nil {
46
                ERROR.Println("String to integer conversion failed for TokenCleanUpFrequency reverting to default value of 180 minutes")
47
                tokenCleanUpFrequencyInteger = 180
48
        }
49
50
        ticker := time.NewTicker(time.Duration(tokenCleanUpFrequencyInteger) * time.Minute)
51
        TRACE.Println("Starting goroutine for token archiving")
52
        go func() {
53
                for {
54
                        select {
55
                        case <-ticker.C:
56
                                ArchiveTokenAfterCutOffTime(&dbConnection)
57
                        case <-quit:
58
                                TRACE.Println("Quit signal: JobArchiveExpiredSessionToken")
59
                        }
60
                }
61
        }()
62
63
}
238 | 239 |
func InitApp
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:

240 |
62
func InitApp(logFileName string, initializeDB *bool) {
63
        var err error
64
        file, err = os.OpenFile(logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
65
        if err != nil {
66
                fmt.Println("Log file cannot be opened")
67
                file.Close()
68
                file = os.Stdout
69
        }
70
71
        InitLogger(file, file, file, file, file)
72
        TRACE.Println("Logger initialized to file " + logFileName)
73
74
        dbConnection = GetDBConnection()
75
        TRACE.Println("Global connection object initialized")
76
77
        TRACE.Println(string(strconv.AppendBool([]byte("Application started with Init DB flag value "), *initializeDB)))
78
79
        if *initializeDB == true {
80
                TRACE.Println("DB Init Start")
81
                InitDB()
82
                TRACE.Println("DB Init Complete")
83
        }
84
85
        LoadAppPropertiesFromDb()
86
87
        dbLoggerPropertyValue, err := GetAppProperties("DbDebugLogs")
88
89
        if err == nil {
90
                dbLoggerFlag, err := strconv.ParseBool(dbLoggerPropertyValue)
91
                if err == nil && dbLoggerFlag {
92
                        dbConnection.LogMode(dbLoggerFlag)
93
                        dbConnection.SetLogger(DATABASE)
94
                        TRACE.Println("Database logger initialized")
95
                }
96
        }
97
98
        StartScheduledJobs()
99
}
241 | 242 |
func GetTimeExtension
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

243 |
215
func GetTimeExtension() int {
216
217
        timeExtensionString, err := GetAppProperties("TimeExtension")
218
        if err != nil {
219
                ERROR.Println(err.Error())
220
                return 0
221
        }
222
223
        extension, conversionErr := strconv.Atoi(timeExtensionString)
224
        if conversionErr != nil {
225
                ERROR.Println(conversionErr.Error())
226
                return 0
227
        }
228
229
        return extension
230
}
244 | 245 |
func @52:5
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:

246 |
52
func() {
53
                for {
54
                        select {
55
                        case <-ticker.C:
56
                                ArchiveTokenAfterCutOffTime(&dbConnection)
57
                        case <-quit:
58
                                TRACE.Println("Quit signal: JobArchiveExpiredSessionToken")
59
                        }
60
                }
61
        }
247 | 248 |
func @24:5
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:

249 |
24
func() {
25
                for {
26
                        select {
27
                        case <-ticker.C:
28
                                LoadAppPropertiesFromDb()
29
                        case <-quit:
30
                                TRACE.Println("Quit signal: JobRefreshAppProperties")
31
                                ticker.Stop()
32
                        }
33
                }
34
        }
250 | 251 |
func ChangePasswordHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

252 |
59
func ChangePasswordHandler(request ChangePasswordRequest, r render.Render) {
60
61
        authenticateUserError := AuthenticateUser(request.Username, request.OldPassword, &dbConnection)
62
        if authenticateUserError == nil || authenticateUserError == FirstLoginPasswordChange {
63
                status, changePasswordError := ChangePassword(request.Username, request.NewPassword, &dbConnection)
64
                if changePasswordError == nil {
65
                        login := LoginCredential{Username: request.Username, Password: request.NewPassword, DeviceId: request.DeviceId}
66
                        sessionToken, err := CreateNewTokenDbEntry(login, &dbConnection)
67
                        if err == nil {
68
                                r.JSON(200, map[string]interface{}{"passwordChanged": true, "sessionToken": sessionToken})
69
                        } else {
70
                                ERROR.Println("DB entry failed for token, returning 500")
71
                                response := ErrorResponse{Status: "Internal Server Error", Message: "Password changed with but encountered: " + SystemError.Error(), Description: ""}
72
                                r.JSON(500, response)
73
                        }
74
                } else {
75
                        var response ErrorResponse
76
                        if status == 401 {
77
                                response = ErrorResponse{Status: "Unauthorized", Message: AuthenticationFailed.Error(), Description: ""}
78
                        } else if status == 500 {
79
                                response = ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""}
80
                        }
81
                        r.JSON(status, response)
82
                }
83
        } else {
84
                response := ErrorResponse{Status: "Unauthorized", Message: AuthenticationFailed.Error(), Description: ""}
85
                r.JSON(401, response)
86
        }
87
88
}
253 | 254 |
func main
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/server.go:

255 |
27
func main() {
28
29
        initializeDB := flag.Bool("initdb", false, "initalizing database")
30
        flag.Parse()
31
32
        InitApp(logFile, initializeDB)
33
34
        m := martini.Classic()
35
        m.Use(render.Renderer())
36
        m.Use(martini.Recovery())
37
38
        //status
39
        m.Get("/status", StatusHandler)
40
41
        //Login end point
42
        m.Post("/auth/login", binding.Bind(LoginCredential{}), LoginHandler)
43
44
        //Create new user
45
        m.Post("/user/create", binding.Bind(CreateUserRequest{}), CreateUserHandler)
46
47
        //Validate unique username
48
        m.Post("/validate/username", binding.Bind(ValidateUsernameRequest{}), ValidateUsernameHandler)
49
50
        //Validate session token
51
        m.Post("/validate/token", binding.Bind(ValidateSessionTokenRequest{}), ValidateSessionTokenHandler)
52
53
        //Change password
54
        m.Post("/user/changePassword", binding.Bind(ChangePasswordRequest{}), ChangePasswordHandler)
55
56
        //Check permission
57
        m.Post("/user/checkPermission", binding.Bind(CheckPermissionRequest{}), CheckPermissionsForUserHandler)
58
59
        appGracefulShutdownTimeinSeconds, err := strconv.Atoi(viper.GetString("appGracefulShutdownTimeinSeconds"))
60
        if err != nil {
61
                ERROR.Panicln("Cannot start the server, shutdown time missing from config file")
62
        }
63
64
        graceful.Run(":"+viper.GetString("appPort"), time.Duration(appGracefulShutdownTimeinSeconds)*time.Second, m)
65
66
        defer cleanUpAfterShutdown()
67
}
256 | 257 |
func InitDB
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:

258 |
16
func InitDB() {
17
18
        dbConnection.AutoMigrate(User{})
19
        dbConnection.AutoMigrate(Permission{})
20
        dbConnection.AutoMigrate(Group{})
21
        dbConnection.AutoMigrate(GroupPermission{})
22
        dbConnection.AutoMigrate(UserGroup{})
23
24
        dbConnection.AutoMigrate(AppProperties{})
25
        dbConnection.AutoMigrate(ActivityLog{})
26
27
        dbConnection.AutoMigrate(Token{})
28
        dbConnection.AutoMigrate(ArchivedToken{})
29
        dbConnection.AutoMigrate(DeviceType{})
30
        dbConnection.AutoMigrate(PasswordRecord{})
31
32
        InsertAppProperties(&dbConnection)
33
        InsertDeviceTypes(&dbConnection)
34
}
259 | 260 |
func LoginHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

261 |
13
func LoginHandler(login LoginCredential, r render.Render) {
14
15
        authenticateUserErr := AuthenticateUser(login.Username, login.Password, &dbConnection)
16
17
        if authenticateUserErr == nil {
18
                sessionToken, err := CreateNewTokenDbEntry(login, &dbConnection)
19
                if err != nil {
20
                        ERROR.Println(DatabaseError.Error() + " :" + login.Username)
21
                        response := ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""}
22
                        r.JSON(500, response)
23
                } else {
24
                        r.JSON(200, map[string]interface{}{"sessionToken": sessionToken})
25
                }
26
        } else if authenticateUserErr == FirstLoginPasswordChange {
27
                response := ErrorResponse{Status: "Forbidden", Message: FirstLoginPasswordChange.Error(), Description: ""}
28
                r.JSON(403, response)
29
        } else {
30
                response := ErrorResponse{Status: "Unauthorized", Message: AuthenticationFailed.Error(), Description: ""}
31
                r.JSON(401, response)
32
        }
33
}
262 | 263 |
func ValidateSessionTokenHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

264 |
90
func ValidateSessionTokenHandler(request ValidateSessionTokenRequest, r render.Render) {
91
92
        err, userId := ValidateSessionToken(request.SessionToken, &dbConnection)
93
94
        if err == nil {
95
                r.JSON(200, map[string]interface{}{"userId": userId})
96
        } else {
97
                ERROR.Println("Error validating sessionToken: " + request.SessionToken + ", error:" + err.Error())
98
                if err == InvalidSessionToken {
99
                        response := ErrorResponse{Status: "Unauthorized", Message: InvalidSessionToken.Error(), Description: ""}
100
                        r.JSON(401, response)
101
                } else if err == ExpiredSessionToken {
102
                        response := ErrorResponse{Status: "Forbidden", Message: ExpiredSessionToken.Error(), Description: ""}
103
                        r.JSON(403, response)
104
                } else {
105
                        response := ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""}
106
                        r.JSON(500, response)
107
                }
108
        }
109
}
265 | 266 |
func ArchiveTokenAfterCutOffTime
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:

267 |
140
func ArchiveTokenAfterCutOffTime(db *gorm.DB) {
141
142
        tokenCutOffTimeString, err := GetAppProperties("TokenCutOffTime")
143
        if err != nil {
144
                ERROR.Println(err.Error())
145
        }
146
147
        tokenCutOffTimeInteger, err := strconv.Atoi(tokenCutOffTimeString)
148
        if err != nil {
149
                ERROR.Println("String to integer conversion failed for TokenCutOffTime, reverting to default value = 30 minutes")
150
                tokenCutOffTimeInteger = 30
151
        }
152
153
        TRACE.Println("Token archiving started at: " + time.Now().String())
154
155
        db.Exec("UPDATE TABLE tokens SET active = ? WHERE (SELECT EXTRACT(MINUTE FROM (? - expires_at)) FROM tokens) > ?", false, time.Now().UTC(), tokenCutOffTimeInteger)
156
157
        db.Exec("INSERT INTO archived_tokens (token, user_id, key, created_at, last_accessed_at, expires_at, device_type_id, active)(SELECT (token, user_id, key, created_at, last_accessed_at, expires_at, device_type_id, active) WHERE active = ?)", false)
158
159
        db.Exec("DELETE FROM tokens WHERE active = ?", false)
160
161
        TRACE.Println("Token archiving completed at: " + time.Now().String())
162
}
268 | 269 |
func CreateUserHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

270 |
42
func CreateUserHandler(request CreateUserRequest, r render.Render) {
43
44
        status, err := CreateNewUser(request, &dbConnection)
45
        if err != nil {
46
                ERROR.Println(err.Error())
47
                var response ErrorResponse
48
                if status == 500 {
49
                        response = ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""}
50
                } else if status == 409 {
51
                        response = ErrorResponse{Status: "Conflict", Message: err.Error(), Description: ""}
52
                }
53
                r.JSON(status, response)
54
        } else {
55
                r.JSON(status, map[string]interface{}{"userCreated": true})
56
        }
57
}
271 | 272 |
func CheckPermissionsForUserHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

273 |
111
func CheckPermissionsForUserHandler(request CheckPermissionRequest, r render.Render) {
112
113
        err := CheckPermissionsForUser(request.UserId, Permission{PermissionDescription: request.PermissionDescription}, &dbConnection)
114
115
        if err == nil {
116
                r.JSON(200, map[string]interface{}{"permissionCheck": true})
117
        } else if err == PermissionDenied {
118
                response := ErrorResponse{Status: "Unauthorized", Message: PermissionDenied.Error(), Description: ""}
119
                r.JSON(401, response)
120
        } else {
121
                response := ErrorResponse{Status: "Internal Server Error", Message: SystemError.Error(), Description: ""}
122
                r.JSON(500, response)
123
        }
124
125
}
274 | 275 |
func ValidateUsernameHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

276 |
35
func ValidateUsernameHandler(usernameRequest ValidateUsernameRequest, r render.Render) {
36
37
        validUsernameFlag := ValidateUniqueUsername(usernameRequest.Username, &dbConnection)
38
39
        r.JSON(200, map[string]interface{}{"valid": validUsernameFlag})
40
}
277 | 278 |
func StatusHandler
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:

279 |
8
func StatusHandler(r render.Render) {
9
        r.JSON(200, map[string]interface{}{"status": Status()})
10
}
280 | 281 |
func Status
Back

In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:

282 |
184
func Status() string {
185
        return "alive at " + time.Now().String()
186
}
283 | 284 | 287 | 288 |
gondalf
289 |
58.61%
290 |
291 | 292 | --------------------------------------------------------------------------------