Commit 077dae33c8d628fad5c60e081dfa2a6651ef1df4

Authored by Marko Tikvić
1 parent d666282954
Exists in master and in 1 other branch v2

removed role constants

Showing 1 changed file with 7 additions and 21 deletions   Show diff stats
1 // TODO: Improve roles 1 // TODO: Improve roles
2 package webutility 2 package webutility
3 3
4 import ( 4 import (
5 "crypto/rand" 5 "crypto/rand"
6 "crypto/sha256" 6 "crypto/sha256"
7 "encoding/hex" 7 "encoding/hex"
8 "errors" 8 "errors"
9 "net/http" 9 "net/http"
10 "strings" 10 "strings"
11 "time" 11 "time"
12 12
13 "github.com/dgrijalva/jwt-go" 13 "github.com/dgrijalva/jwt-go"
14 ) 14 )
15 15
16 const OneDay = time.Hour * 24 16 const OneDay = time.Hour * 24
17 const OneWeek = OneDay * 7 17 const OneWeek = OneDay * 7
18 const saltSize = 32 18 const saltSize = 32
19 const appName = "korisnicki-centar" 19 const appName = "korisnicki-centar"
20 const secret = "korisnicki-centar-api" 20 const secret = "korisnicki-centar-api"
21 21
22 const RoleAdmin string = "ADMINISTRATOR"
23 const RoleManager string = "RUKOVODILAC"
24 const RoleReporter string = "REPORTER"
25 const RoleOperator string = "OPERATER"
26 const RoleAdminID uint32 = 1
27 const RoleManagerID uint32 = 2
28 const RoleReporterID uint32 = 3
29 const RoleOperatorID uint32 = 4
30
31 type Role struct { 22 type Role struct {
32 name string 23 Name string `json:"name"`
33 id uint32 24 ID uint32 `json:"id"`
34 } 25 }
35 26
36 // TokenClaims are JWT token claims. 27 // TokenClaims are JWT token claims.
37 type TokenClaims struct { 28 type TokenClaims struct {
38 Username string `json:"username"` 29 Username string `json:"username"`
39 Role string `json:"role"` 30 Role string `json:"role"`
40 RoleID uint32 `json:"roleID"` 31 RoleID uint32 `json:"roleID"`
41 jwt.StandardClaims 32 jwt.StandardClaims
42 } 33 }
43 34
44 // CredentialsStruct is an instace of username/password values. 35 // CredentialsStruct is an instace of username/password values.
45 type CredentialsStruct struct { 36 type CredentialsStruct struct {
46 Username string `json:"username"` 37 Username string `json:"username"`
47 Password string `json:"password"` 38 Password string `json:"password"`
48 } 39 }
49 40
50 var admin Role = Role{RoleAdmin, RoleAdminID}
51 var manager Role = Role{RoleManager, RoleManagerID}
52 var reporter Role = Role{RoleReporter, RoleReporterID}
53 var operator Role = Role{RoleOperator, RoleOperatorID}
54
55 // generateSalt returns a string of random characters of 'saltSize' length. 41 // generateSalt returns a string of random characters of 'saltSize' length.
56 func generateSalt() (salt string, err error) { 42 func generateSalt() (salt string, err error) {
57 rawsalt := make([]byte, saltSize) 43 rawsalt := make([]byte, saltSize)
58 44
59 _, err = rand.Read(rawsalt) 45 _, err = rand.Read(rawsalt)
60 if err != nil { 46 if err != nil {
61 return "", err 47 return "", err
62 } 48 }
63 49
64 salt = hex.EncodeToString(rawsalt) 50 salt = hex.EncodeToString(rawsalt)
65 return salt, nil 51 return salt, nil
66 } 52 }
67 53
68 // HashString hashes input string with SHA256 algorithm. 54 // HashString hashes input string using SHA256.
69 // If the presalt parameter is not provided HashString will generate new salt string. 55 // If the presalt parameter is not provided HashString will generate new salt string.
70 // Returns hash and salt string or an error if it fails. 56 // Returns hash and salt string or an error if it fails.
71 func HashString(str string, presalt string) (hash, salt string, err error) { 57 func HashString(str, presalt string) (hash, salt string, err error) {
72 // chech if message is presalted 58 // chech if message is presalted
73 if presalt == "" { 59 if presalt == "" {
74 salt, err = generateSalt() 60 salt, err = generateSalt()
75 if err != nil { 61 if err != nil {
76 return "", "", err 62 return "", "", err
77 } 63 }
78 } else { 64 } else {
79 salt = presalt 65 salt = presalt
80 } 66 }
81 67
82 // convert strings to raw byte slices 68 // convert strings to raw byte slices
83 rawstr := []byte(str) 69 rawstr := []byte(str)
84 rawsalt, err := hex.DecodeString(salt) 70 rawsalt, err := hex.DecodeString(salt)
85 if err != nil { 71 if err != nil {
86 return "", "", err 72 return "", "", err
87 } 73 }
88 74
89 rawdata := make([]byte, len(rawstr)+len(rawsalt)) 75 rawdata := make([]byte, len(rawstr)+len(rawsalt))
90 rawdata = append(rawdata, rawstr...) 76 rawdata = append(rawdata, rawstr...)
91 rawdata = append(rawdata, rawsalt...) 77 rawdata = append(rawdata, rawsalt...)
92 78
93 // hash message + salt 79 // hash message + salt
94 hasher := sha256.New() 80 hasher := sha256.New()
95 hasher.Write(rawdata) 81 hasher.Write(rawdata)
96 rawhash := hasher.Sum(nil) 82 rawhash := hasher.Sum(nil)
97 83
98 hash = hex.EncodeToString(rawhash) 84 hash = hex.EncodeToString(rawhash)
99 return hash, salt, nil 85 return hash, salt, nil
100 } 86 }
101 87
102 // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. 88 // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims.
103 // It returns an error if it fails. 89 // It returns an error if it fails.
104 func CreateAPIToken(username, role string, roleID uint32) (string, error) { 90 func CreateAPIToken(username string, role Role) (string, error) {
105 var apiToken string 91 var apiToken string
106 var err error 92 var err error
107 93
108 if err != nil { 94 if err != nil {
109 return "", err 95 return "", err
110 } 96 }
111 97
112 claims := TokenClaims{ 98 claims := TokenClaims{
113 username, 99 username,
114 role, 100 role.Name,
115 roleID, 101 role.ID,
116 jwt.StandardClaims{ 102 jwt.StandardClaims{
117 ExpiresAt: (time.Now().Add(OneWeek)).Unix(), 103 ExpiresAt: (time.Now().Add(OneWeek)).Unix(),
118 Issuer: appName, 104 Issuer: appName,
119 }, 105 },
120 } 106 }
121 107
122 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 108 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
123 apiToken, err = jwtToken.SignedString([]byte(secret)) 109 apiToken, err = jwtToken.SignedString([]byte(secret))
124 if err != nil { 110 if err != nil {
125 return "", err 111 return "", err
126 } 112 }
127 return apiToken, nil 113 return apiToken, nil
128 } 114 }
129 115
130 // RefreshAPIToken prolongs JWT token's expiration date for one week. 116 // RefreshAPIToken prolongs JWT token's expiration date for one week.
131 // It returns new JWT token or an error if it fails. 117 // It returns new JWT token or an error if it fails.
132 func RefreshAPIToken(tokenString string) (string, error) { 118 func RefreshAPIToken(tokenString string) (string, error) {
133 var newToken string 119 var newToken string
134 tokenString = strings.TrimPrefix(tokenString, "Bearer ") 120 tokenString = strings.TrimPrefix(tokenString, "Bearer ")
135 token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) 121 token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc)
136 if err != nil { 122 if err != nil {
137 return "", err 123 return "", err
138 } 124 }
139 125
140 // type assertion 126 // type assertion
141 claims, ok := token.Claims.(*TokenClaims) 127 claims, ok := token.Claims.(*TokenClaims)
142 if !ok || !token.Valid { 128 if !ok || !token.Valid {
143 return "", errors.New("token is not valid") 129 return "", errors.New("token is not valid")
144 } 130 }
145 131
146 claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() 132 claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix()
147 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 133 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
148 134
149 newToken, err = jwtToken.SignedString([]byte(secret)) 135 newToken, err = jwtToken.SignedString([]byte(secret))
150 if err != nil { 136 if err != nil {
151 return "", err 137 return "", err
152 } 138 }
153 139
154 return newToken, nil 140 return newToken, nil
155 } 141 }
156 142
157 // ParseAPIToken parses JWT token claims. 143 // ParseAPIToken parses JWT token claims.
158 // It returns a pointer to TokenClaims struct or an error if it fails. 144 // It returns a pointer to TokenClaims struct or an error if it fails.
159 func ParseAPIToken(tokenString string) (*TokenClaims, error) { 145 func ParseAPIToken(tokenString string) (*TokenClaims, error) {
160 if ok := strings.HasPrefix(tokenString, "Bearer "); ok { 146 if ok := strings.HasPrefix(tokenString, "Bearer "); ok {
161 tokenString = strings.TrimPrefix(tokenString, "Bearer ") 147 tokenString = strings.TrimPrefix(tokenString, "Bearer ")
162 } else { 148 } else {
163 return &TokenClaims{}, errors.New("Authorization header is incomplete") 149 return &TokenClaims{}, errors.New("Authorization header is incomplete")
164 } 150 }
165 151
166 token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) 152 token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc)
167 if err != nil { 153 if err != nil {
168 return &TokenClaims{}, err 154 return &TokenClaims{}, err
169 } 155 }
170 156
171 // type assertion 157 // type assertion
172 claims, ok := token.Claims.(*TokenClaims) 158 claims, ok := token.Claims.(*TokenClaims)
173 if !ok || !token.Valid { 159 if !ok || !token.Valid {
174 return &TokenClaims{}, errors.New("token is not valid") 160 return &TokenClaims{}, errors.New("token is not valid")
175 } 161 }
176 return claims, nil 162 return claims, nil
177 } 163 }
178 164
179 func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { 165 func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) {
180 token := r.Header.Get("Authorization") 166 token := r.Header.Get("Authorization")
181 if ok := strings.HasPrefix(token, "Bearer "); ok { 167 if ok := strings.HasPrefix(token, "Bearer "); ok {
182 token = strings.TrimPrefix(token, "Bearer ") 168 token = strings.TrimPrefix(token, "Bearer ")
183 } else { 169 } else {
184 return &TokenClaims{}, errors.New("Authorization header is incomplete") 170 return &TokenClaims{}, errors.New("Authorization header is incomplete")
185 } 171 }
186 172
187 parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) 173 parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc)
188 if err != nil { 174 if err != nil {
189 return &TokenClaims{}, err 175 return &TokenClaims{}, err
190 } 176 }
191 177
192 // type assertion 178 // type assertion
193 claims, ok := parsedToken.Claims.(*TokenClaims) 179 claims, ok := parsedToken.Claims.(*TokenClaims)
194 if !ok || !parsedToken.Valid { 180 if !ok || !parsedToken.Valid {
195 return &TokenClaims{}, errors.New("token is not valid") 181 return &TokenClaims{}, errors.New("token is not valid")
196 } 182 }
197 return claims, err 183 return claims, err
198 } 184 }
199 185
200 // secretFunc returns byte slice of API secret keyword. 186 // secretFunc returns byte slice of API secret keyword.
201 func secretFunc(token *jwt.Token) (interface{}, error) { 187 func secretFunc(token *jwt.Token) (interface{}, error) {
202 return []byte(secret), nil 188 return []byte(secret), nil
203 } 189 }
204 190
205 // RbacCheck returns true if role that made HTTP request is authorized to 191 // RbacCheck returns true if role that made HTTP request is authorized to
206 // access the resource it is targeting. 192 // access the resource it is targeting.
207 // It exctracts user's role from the JWT token located in Authorization header of 193 // It exctracts user's role from the JWT token located in Authorization header of
208 // http.Request and then compares it with the list of supplied roles and returns 194 // http.Request and then compares it with the list of supplied roles and returns
209 // true if there's a match, if "*" is provided or if the authRoles is nil. 195 // true if there's a match, if "*" is provided or if the authRoles is nil.
210 // Otherwise it returns false. 196 // Otherwise it returns false.
211 func RbacCheck(req *http.Request, authRoles []string) bool { 197 func RbacCheck(req *http.Request, authRoles []string) bool {
212 if authRoles == nil { 198 if authRoles == nil {
213 return true 199 return true
214 } 200 }
215 201
216 token := req.Header.Get("Authorization") 202 token := req.Header.Get("Authorization")
217 claims, err := ParseAPIToken(token) 203 claims, err := ParseAPIToken(token)
218 if err != nil { 204 if err != nil {
219 return false 205 return false
220 } 206 }
221 207
222 for _, r := range authRoles { 208 for _, r := range authRoles {
223 if claims.Role == r || r == "*" { 209 if claims.Role == r || r == "*" {
224 return true 210 return true
225 } 211 }
226 } 212 }
227 213
228 return false 214 return false
229 } 215 }
230 216
231 // Rbac sets common headers and performs RBAC. 217 // Rbac sets common headers and performs RBAC.
232 // If RBAC passes it calls the handlerFunc. 218 // If RBAC passes it calls the handlerFunc.
233 func RbacHandler(handlerFunc http.HandlerFunc, authRoles []string) http.HandlerFunc { 219 func RbacHandler(handlerFunc http.HandlerFunc, authRoles []string) http.HandlerFunc {
234 return func(w http.ResponseWriter, req *http.Request) { 220 return func(w http.ResponseWriter, req *http.Request) {
235 w.Header().Set("Access-Control-Allow-Origin", "*") 221 w.Header().Set("Access-Control-Allow-Origin", "*")
236 222
237 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") 223 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
238 224
239 w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, 225 w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type,
240 Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) 226 Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`)
241 227
242 w.Header().Set("Content-Type", "application/json; charset=utf-8") 228 w.Header().Set("Content-Type", "application/json; charset=utf-8")
243 229
244 // TODO: Check for content type 230 // TODO: Check for content type
245 231
246 if req.Method == "OPTIONS" { 232 if req.Method == "OPTIONS" {
247 return 233 return
248 } 234 }
249 235
250 err := req.ParseForm() 236 err := req.ParseForm()
251 if err != nil { 237 if err != nil {
252 BadRequestResponse(w, req) 238 BadRequestResponse(w, req)
253 return 239 return
254 } 240 }
255 241
256 if !RbacCheck(req, authRoles) { 242 if !RbacCheck(req, authRoles) {
257 UnauthorizedResponse(w, req) 243 UnauthorizedResponse(w, req)
258 return 244 return
259 } 245 }
260 246
261 // execute HandlerFunc 247 // execute HandlerFunc
262 handlerFunc(w, req) 248 handlerFunc(w, req)
263 } 249 }
264 } 250 }
265 251