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