├── 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 |
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.
ValidateUniqueUsername(...) | gondalf/handlerUtils.go | 100.00% | 6/6 |
InitLogger(...) | gondalf/util.go | 100.00% | 5/5 |
generateSessionToken(...) | gondalf/handlerUtils.go | 100.00% | 4/4 |
GetAppProperties(...) | gondalf/util.go | 100.00% | 4/4 |
cleanUpAfterShutdown(...) | gondalf/server.go | 100.00% | 3/3 |
LoadAppPropertiesFromDb(...) | gondalf/util.go | 100.00% | 2/2 |
StartScheduledJobs(...) | gondalf/scheduledJobs.go | 100.00% | 2/2 |
GetDBConnection(...) | gondalf/db.go | 94.74% | 18/19 |
InsertAppProperties(...) | gondalf/db.go | 91.67% | 11/12 |
CheckPermissionsForUser(...) | gondalf/handlerUtils.go | 90.48% | 19/21 |
CreateNewTokenDbEntry(...) | gondalf/handlerUtils.go | 90.00% | 18/20 |
CreateNewUser(...) | gondalf/handlerUtils.go | 88.89% | 16/18 |
InsertDeviceTypes(...) | gondalf/db.go | 87.50% | 7/8 |
JobRefreshAppProperties(...) | gondalf/scheduledJobs.go | 85.71% | 6/7 |
LoadConfigurationFromFile(...) | gondalf/util.go | 83.33% | 5/6 |
AuthenticateUser(...) | gondalf/handlerUtils.go | 82.61% | 19/23 |
ValidateSessionToken(...) | gondalf/handlerUtils.go | 82.35% | 14/17 |
UpdatePasswordRecordLoginCount(...) | gondalf/handlerUtils.go | 77.78% | 7/9 |
GetTimeOutValue(...) | gondalf/handlerUtils.go | 76.47% | 13/17 |
EncryptPassword(...) | gondalf/handlerUtils.go | 75.00% | 3/4 |
ChangePassword(...) | gondalf/handlerUtils.go | 71.43% | 15/21 |
JobArchiveExpiredSessionToken(...) | gondalf/scheduledJobs.go | 70.00% | 7/10 |
InitApp(...) | gondalf/util.go | 62.50% | 15/24 |
GetTimeExtension(...) | gondalf/handlerUtils.go | 55.56% | 5/9 |
@52:5(...) | gondalf/scheduledJobs.go | 50.00% | 2/4 |
@24:5(...) | gondalf/scheduledJobs.go | 40.00% | 2/5 |
ChangePasswordHandler(...) | gondalf/handlers.go | 0.00% | 0/19 |
main(...) | gondalf/server.go | 0.00% | 0/18 |
InitDB(...) | gondalf/db.go | 0.00% | 0/13 |
LoginHandler(...) | gondalf/handlers.go | 0.00% | 0/13 |
ValidateSessionTokenHandler(...) | gondalf/handlers.go | 0.00% | 0/12 |
ArchiveTokenAfterCutOffTime(...) | gondalf/util.go | 0.00% | 0/12 |
CreateUserHandler(...) | gondalf/handlers.go | 0.00% | 0/10 |
CheckPermissionsForUserHandler(...) | gondalf/handlers.go | 0.00% | 0/8 |
ValidateUsernameHandler(...) | gondalf/handlers.go | 0.00% | 0/2 |
StatusHandler(...) | gondalf/handlers.go | 0.00% | 0/1 |
Status(...) | gondalf/handlerUtils.go | 0.00% | 0/1 |
gondalf | 58.61% | 228/389 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 161 | |
| 162 | |
| 163 | |
| 164 | |
| 165 | |
| 166 | |
| 167 | |
| 168 | |
| 169 | |
| 170 | |
| 171 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | |
| 106 | |
| 107 | |
| 108 | |
| 109 | |
| 110 | |
| 111 | |
| 112 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 121 | |
| 122 | |
| 123 | |
| 124 | |
| 125 | |
| 126 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | |
| 119 | |
| 120 | |
| 121 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/server.go:
| 69 | |
| 70 | |
| 71 | |
| 72 | |
| 73 | |
| 74 | |
| 75 | |
| 76 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:
| 135 | |
| 136 | |
| 137 | |
| 138 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:
| 9 | |
| 10 | |
| 11 | |
| 12 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | |
| 86 | |
| 87 | |
| 88 | |
| 89 | |
| 90 | |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
| 100 | |
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | |
| 106 | |
| 107 | |
| 108 | |
| 109 | |
| 110 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 288 | |
| 289 | |
| 290 | |
| 291 | |
| 292 | |
| 293 | |
| 294 | |
| 295 | |
| 296 | |
| 297 | |
| 298 | |
| 299 | |
| 300 | |
| 301 | |
| 302 | |
| 303 | |
| 304 | |
| 305 | |
| 306 | |
| 307 | |
| 308 | |
| 309 | |
| 310 | |
| 311 | |
| 312 | |
| 313 | |
| 314 | |
| 315 | |
| 316 | |
| 317 | |
| 318 | |
| 319 | |
| 320 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | |
| 73 | |
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 128 | |
| 129 | |
| 130 | |
| 131 | |
| 132 | |
| 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 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:
| 123 | |
| 124 | |
| 125 | |
| 126 | |
| 127 | |
| 128 | |
| 129 | |
| 130 | |
| 131 | |
| 132 | |
| 133 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | |
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 188 | |
| 189 | |
| 190 | |
| 191 | |
| 192 | |
| 193 | |
| 194 | |
| 195 | |
| 196 | |
| 197 | |
| 198 | |
| 199 | |
| 200 | |
| 201 | |
| 202 | |
| 203 | |
| 204 | |
| 205 | |
| 206 | |
| 207 | |
| 208 | |
| 209 | |
| 210 | |
| 211 | |
| 212 | |
| 213 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 268 | |
| 269 | |
| 270 | |
| 271 | |
| 272 | |
| 273 | |
| 274 | |
| 275 | |
| 276 | |
| 277 | |
| 278 | |
| 279 | |
| 280 | |
| 281 | |
| 282 | |
| 283 | |
| 284 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 88 | |
| 89 | |
| 90 | |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
| 100 | |
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | |
| 106 | |
| 107 | |
| 108 | |
| 109 | |
| 110 | |
| 111 | |
| 112 | |
| 113 | |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 173 | |
| 174 | |
| 175 | |
| 176 | |
| 177 | |
| 178 | |
| 179 | |
| 180 | |
| 181 | |
| 182 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 232 | |
| 233 | |
| 234 | |
| 235 | |
| 236 | |
| 237 | |
| 238 | |
| 239 | |
| 240 | |
| 241 | |
| 242 | |
| 243 | |
| 244 | |
| 245 | |
| 246 | |
| 247 | |
| 248 | |
| 249 | |
| 250 | |
| 251 | |
| 252 | |
| 253 | |
| 254 | |
| 255 | |
| 256 | |
| 257 | |
| 258 | |
| 259 | |
| 260 | |
| 261 | |
| 262 | |
| 263 | |
| 264 | |
| 265 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | |
| 73 | |
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | |
| 86 | |
| 87 | |
| 88 | |
| 89 | |
| 90 | |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 215 | |
| 216 | |
| 217 | |
| 218 | |
| 219 | |
| 220 | |
| 221 | |
| 222 | |
| 223 | |
| 224 | |
| 225 | |
| 226 | |
| 227 | |
| 228 | |
| 229 | |
| 230 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/scheduledJobs.go:
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | |
| 73 | |
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | |
| 86 | |
| 87 | |
| 88 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/server.go:
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | |
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/db.go:
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 90 | |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
| 100 | |
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | |
| 106 | |
| 107 | |
| 108 | |
| 109 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/util.go:
| 140 | |
| 141 | |
| 142 | |
| 143 | |
| 144 | |
| 145 | |
| 146 | |
| 147 | |
| 148 | |
| 149 | |
| 150 | |
| 151 | |
| 152 | |
| 153 | |
| 154 | |
| 155 | |
| 156 | |
| 157 | |
| 158 | |
| 159 | |
| 160 | |
| 161 | |
| 162 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 111 | |
| 112 | |
| 113 | |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | |
| 119 | |
| 120 | |
| 121 | |
| 122 | |
| 123 | |
| 124 | |
| 125 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 35 | |
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlers.go:
| 8 | |
| 9 | |
| 10 | |
In /Users/absharma/Desktop/code/gocode/src/gondalf/handlerUtils.go:
| 184 | |
| 185 | |
| 186 | |