Commit 33fd58161a627305c9c343cb31ea018b4b5d0ac0
1 parent
437859ae90
Exists in
master
and in
1 other branch
minor changes, should update dependant apps
Showing
4 changed files
with
63 additions
and
92 deletions
Show diff stats
auth_utility.go
1 | package restutility | 1 | package restutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "errors" | 4 | "errors" |
5 | "time" | 5 | "time" |
6 | "crypto/sha256" | 6 | "crypto/sha256" |
7 | "crypto/rand" | 7 | "crypto/rand" |
8 | "encoding/hex" | 8 | "encoding/hex" |
9 | "strings" | 9 | "strings" |
10 | "github.com/dgrijalva/jwt-go" | 10 | "github.com/dgrijalva/jwt-go" |
11 | ) | 11 | ) |
12 | 12 | ||
13 | const OneDay = time.Hour*24 | 13 | const OneDay = time.Hour*24 |
14 | const OneWeek = OneDay*7 | 14 | const OneWeek = OneDay*7 |
15 | const saltSize = 32 | 15 | const saltSize = 32 |
16 | const appName = "korisnicki-centar" | 16 | const appName = "korisnicki-centar" |
17 | const secret = "korisnicki-centar-api" | 17 | const secret = "korisnicki-centar-api" |
18 | 18 | ||
19 | type TokenClaims struct { | 19 | type TokenClaims struct { |
20 | Username string `json:"username"` | 20 | Username string `json:"username"` |
21 | Role string `json:"role"` | 21 | Role string `json:"role"` |
22 | jwt.StandardClaims | 22 | jwt.StandardClaims |
23 | } | 23 | } |
24 | 24 | ||
25 | type CredentialsStruct struct { | 25 | type CredentialsStruct struct { |
26 | Username string `json:"username"` | 26 | Username string `json:"username"` |
27 | Password string `json:"password"` | 27 | Password string `json:"password"` |
28 | } | 28 | } |
29 | 29 | ||
30 | func GenerateSalt() (string, error) { | 30 | func generateSalt() (salt string, error) { |
31 | salt := "" | ||
32 | rawsalt := make([]byte, saltSize) | 31 | rawsalt := make([]byte, saltSize) |
32 | |||
33 | _, err := rand.Read(rawsalt) | 33 | _, err := rand.Read(rawsalt) |
34 | if err != nil { | 34 | if err != nil { |
35 | return "", err | 35 | return "", err |
36 | } | 36 | } |
37 | |||
37 | salt = hex.EncodeToString(rawsalt) | 38 | salt = hex.EncodeToString(rawsalt) |
38 | return salt, nil | 39 | return salt, nil |
39 | } | 40 | } |
40 | 41 | ||
41 | func HashMessage(message string, presalt string) (string, string, error) { | 42 | func HashString(str string, presalt string) (hash, salt string, err error) { |
42 | hash, salt := "", "" | ||
43 | var err error | ||
44 | |||
45 | // chech if message is presalted | 43 | // chech if message is presalted |
46 | if presalt == "" { | 44 | if presalt == "" { |
47 | salt, err = GenerateSalt() | 45 | salt, err = generateSalt() |
48 | if err != nil { | 46 | if err != nil { |
49 | return "", "", err | 47 | return "", "", err |
50 | } | 48 | } |
51 | } else { | 49 | } else { |
52 | salt = presalt | 50 | salt = presalt |
53 | } | 51 | } |
54 | 52 | ||
55 | // convert strings to raw byte slices | 53 | // convert strings to raw byte slices |
56 | rawmessage := []byte(message) | 54 | rawstr := []byte(str) |
57 | rawsalt, err := hex.DecodeString(salt) | 55 | rawsalt, err := hex.DecodeString(salt) |
58 | if err != nil { | 56 | if err != nil { |
59 | return "", "", err | 57 | return "", "", err |
60 | } | 58 | } |
61 | rawdata := make([]byte, len(rawmessage) + len(rawsalt)) | 59 | |
62 | rawdata = append(rawdata, rawmessage...) | 60 | rawdata := make([]byte, len(rawstr) + len(rawsalt)) |
61 | rawdata = append(rawdata, rawstr...) | ||
63 | rawdata = append(rawdata, rawsalt...) | 62 | rawdata = append(rawdata, rawsalt...) |
64 | 63 | ||
65 | // hash message + salt | 64 | // hash message + salt |
66 | hasher := sha256.New() | 65 | hasher := sha256.New() |
67 | hasher.Write(rawdata) | 66 | hasher.Write(rawdata) |
68 | rawhash := hasher.Sum(nil) | 67 | rawhash := hasher.Sum(nil) |
68 | |||
69 | hash = hex.EncodeToString(rawhash) | 69 | hash = hex.EncodeToString(rawhash) |
70 | return hash, salt, nil | 70 | return hash, salt, nil |
71 | } | 71 | } |
72 | 72 | ||
73 | func IssueAPIToken(username, role string) (string, error) { | 73 | func CreateAPIToken(username, role string) (string, error) { |
74 | var apiToken string | 74 | var apiToken string |
75 | var err error | 75 | var err error |
76 | 76 | ||
77 | if err != nil { | 77 | if err != nil { |
78 | return "", err | 78 | return "", err |
79 | } | 79 | } |
80 | 80 | ||
81 | claims := TokenClaims{ | 81 | claims := TokenClaims{ |
82 | username, | 82 | username, |
83 | role, | 83 | role, |
84 | jwt.StandardClaims{ | 84 | jwt.StandardClaims{ |
85 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), | 85 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), |
86 | Issuer: appName, | 86 | Issuer: appName, |
87 | }, | 87 | }, |
88 | } | 88 | } |
89 | 89 | ||
90 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 90 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
91 | apiToken, err = jwtToken.SignedString([]byte(secret)) | 91 | apiToken, err = jwtToken.SignedString([]byte(secret)) |
92 | if err != nil { | 92 | if err != nil { |
93 | return "", err | 93 | return "", err |
94 | } | 94 | } |
95 | return apiToken, nil | 95 | return apiToken, nil |
96 | } | 96 | } |
97 | 97 | ||
98 | func RefreshAPIToken(tokenString string) (string, error) { | 98 | func RefreshAPIToken(tokenString string) (string, error) { |
99 | var newToken string | 99 | var newToken string |
100 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 100 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
101 | token, err := parseTokenFunc(tokenString) | 101 | token, err := parseTokenFunc(tokenString) |
102 | if err != nil { | 102 | if err != nil { |
103 | return "", err | 103 | return "", err |
104 | } | 104 | } |
105 | 105 | ||
106 | // type assertion | 106 | // type assertion |
107 | claims, ok := token.Claims.(*TokenClaims) | 107 | claims, ok := token.Claims.(*TokenClaims) |
108 | if !ok || !token.Valid { | 108 | if !ok || !token.Valid { |
109 | return "", errors.New("token is not valid") | 109 | return "", errors.New("token is not valid") |
110 | } | 110 | } |
111 | 111 | ||
112 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() | 112 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() |
113 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 113 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
114 | 114 | ||
115 | newToken, err = jwtToken.SignedString([]byte(secret)) | 115 | newToken, err = jwtToken.SignedString([]byte(secret)) |
116 | if err != nil { | 116 | if err != nil { |
117 | return "", err | 117 | return "", err |
118 | } | 118 | } |
119 | 119 | ||
120 | return newToken, nil | 120 | return newToken, nil |
121 | } | 121 | } |
122 | 122 | ||
123 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { | 123 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { |
124 | if ok := strings.HasPrefix(tokenString, "Bearer"); ok { | 124 | if ok := strings.HasPrefix(tokenString, "Bearer"); ok { |
125 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 125 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
126 | } else { | 126 | } else { |
127 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 127 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
128 | } | 128 | } |
129 | 129 | ||
130 | token, err := parseTokenFunc(tokenString) | 130 | token, err := parseTokenFunc(tokenString) |
131 | if err != nil { | 131 | if err != nil { |
132 | return &TokenClaims{}, err | 132 | return &TokenClaims{}, err |
133 | } | 133 | } |
134 | 134 | ||
135 | // type assertion | 135 | // type assertion |
136 | claims, ok := token.Claims.(*TokenClaims) | 136 | claims, ok := token.Claims.(*TokenClaims) |
137 | if !ok || !token.Valid { | 137 | if !ok || !token.Valid { |
138 | return &TokenClaims{}, errors.New("token is not valid") | 138 | return &TokenClaims{}, errors.New("token is not valid") |
139 | } | 139 | } |
140 | return claims, nil | 140 | return claims, nil |
141 | } | 141 | } |
142 | 142 | ||
143 | func parseTokenFunc(tokenString string) (*jwt.Token, error) { | 143 | func parseTokenFunc(tokenString string) (*jwt.Token, error) { |
144 | token, err := jwt.ParseWithClaims(tokenString, | 144 | token, err := jwt.ParseWithClaims(tokenString, |
145 | &TokenClaims{}, | 145 | &TokenClaims{}, |
146 | func(token *jwt.Token) (interface{}, error) { | 146 | func(token *jwt.Token) (interface{}, error) { |
147 | return []byte(secret), nil | 147 | return []byte(secret), nil |
148 | }, | 148 | }, |
format_utility.go
1 | package restutility | 1 | package restutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "time" | 4 | "time" |
5 | ) | 5 | ) |
6 | 6 | ||
7 | func UnixToDate(input int64) time.Time { | 7 | func UnixToDate(unix int64) time.Time { |
8 | return time.Unix(input, 0) | 8 | return time.Unix(unix, 0) |
9 | } | 9 | } |
10 | 10 | ||
11 | func DateToUnix(input interface{}) int64 { | 11 | func DateToUnix(date interface{}) int64 { |
12 | if input != nil { | 12 | if date != nil { |
13 | t := input.(time.Time) | 13 | t := date.(time.Time) |
14 | return t.Unix() | 14 | return t.Unix() |
15 | 15 | ||
16 | } | 16 | } |
17 | return 0 | 17 | return 0 |
18 | } | 18 | } |
19 | 19 | ||
20 | func EqualQuotes(input string) string { | 20 | func EqualQuotes(stmt string) string { |
21 | if input != "" { | 21 | if stmt != "" { |
22 | return " = '" + input + "'" | 22 | stmt = " = '" + stmt + "'" |
23 | } | 23 | } |
24 | return "" | 24 | return stmt |
25 | } | 25 | } |
26 | 26 | ||
27 | func LikeQuotes(input string) string { | 27 | func LikeQuotes(stmt string) string { |
28 | if input != "" { | 28 | if stmt != "" { |
29 | return " LIKE UPPER('%" + input + "%')" | 29 | stmt " LIKE UPPER('%" + stmt + "%')" |
30 | } | 30 | } |
31 | return "" | 31 | return stmt |
32 | } | 32 | } |
33 | 33 | ||
34 | 34 |
http_utility.go
1 | package restutility | 1 | package restutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "net/http" | 4 | "net/http" |
5 | "encoding/json" | 5 | "encoding/json" |
6 | ) | 6 | ) |
7 | 7 | ||
8 | var _apiVersion = "/api/v1" | ||
9 | var _authEndPoint = "/token" | ||
10 | |||
11 | func SetApiVersion(ver string) string { | ||
12 | _apiVersion = ver | ||
13 | return _apiVersion | ||
14 | } | ||
15 | |||
16 | func SetAuthEndpoint(ep string) { | ||
17 | _authEndPoint = ep | ||
18 | } | ||
19 | |||
20 | const templateHttpErr500_EN = "An internal server error has occurred." | 8 | const templateHttpErr500_EN = "An internal server error has occurred." |
21 | const templateHttpErr500_RS = "Došlo je do greške na serveru." | 9 | const templateHttpErr500_RS = "Došlo je do greške na serveru." |
22 | const templateHttpErr400_EN = "Bad request: invalid request body." | 10 | const templateHttpErr400_EN = "Bad request: invalid request body." |
23 | const templateHttpErr400_RS = "Neispravan zahtev." | 11 | const templateHttpErr400_RS = "Neispravan zahtev." |
12 | const templateHttpErr401_EN = "Unauthorized request." | ||
13 | const templateHttpErr401_RS = "Neautorizovan zahtev." | ||
24 | 14 | ||
25 | type HttpError struct { | 15 | type httpError struct { |
26 | Error []HttpErrorDesc `json:"error"` | 16 | Error []HttpErrorDesc `json:"error"` |
27 | Request string `json:"request"` | 17 | Request string `json:"request"` |
28 | } | 18 | } |
29 | 19 | ||
30 | type HttpErrorDesc struct { | 20 | type HttpErrorDesc struct { |
31 | Lang string `json:"lang"` | 21 | Lang string `json:"lang"` |
32 | Desc string `json:"description"` | 22 | Desc string `json:"description"` |
33 | } | 23 | } |
34 | 24 | ||
35 | func RespondWithHttpError(w http.ResponseWriter, | 25 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
36 | req *http.Request, | 26 | err := httpError{ desc, r.Method + " " + r.URL.Path } |
37 | code int, | ||
38 | httpErr []HttpErrorDesc) { | ||
39 | |||
40 | err := HttpError{ | ||
41 | Error: httpErr, | ||
42 | Request: req.Method + " " + req.URL.Path, | ||
43 | } | ||
44 | w.WriteHeader(code) | 27 | w.WriteHeader(code) |
45 | json.NewEncoder(w).Encode(err) | 28 | json.NewEncoder(w).Encode(err) |
46 | } | 29 | } |
47 | 30 | ||
48 | func RespondWithHttpError400(w http.ResponseWriter, req *http.Request) { | 31 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
49 | RespondWithHttpError(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 32 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
50 | {Lang: "en", Desc: templateHttpErr400_EN}, | 33 | { "en", templateHttpErr400_EN }, |
51 | {Lang: "rs", Desc: templateHttpErr400_RS}, | 34 | { "rs", templateHttpErr400_RS }, |
52 | }) | 35 | }) |
53 | } | 36 | } |
54 | 37 | ||
55 | func RespondWithHttpError500(w http.ResponseWriter, req *http.Request) { | 38 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
56 | RespondWithHttpError(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 39 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
57 | {Lang: "en", Desc: templateHttpErr500_EN}, | 40 | { "en", templateHttpErr500_EN }, |
58 | {Lang: "rs", Desc: templateHttpErr500_RS}, | 41 | { "rs", templateHttpErr500_RS }, |
59 | }) | 42 | }) |
60 | } | 43 | } |
61 | 44 | ||
62 | //TODO: Add parameters to enable/disable roles authorization checks | 45 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
46 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ | ||
47 | { "en", templateHttpErr500_EN }, | ||
48 | { "rs", templateHttpErr500_RS }, | ||
49 | }) | ||
50 | } | ||
51 | |||
52 | // TODO: Add parameters to enable/disable roles authorization checks | ||
53 | // TODO: Check for content type | ||
63 | // Sets common headers and checks for token validity. | 54 | // Sets common headers and checks for token validity. |
64 | func HttpPreProc(handlerFunc http.HandlerFunc, authEnabled bool) http.HandlerFunc { | 55 | func WrapHandler(handlerFunc http.HandlerFunc, needauth bool) http.HandlerFunc { |
65 | return func(w http.ResponseWriter, req *http.Request) { | 56 | return func(w http.ResponseWriter, req *http.Request) { |
66 | // @TODO: check Content-type header (must be application/json) | ||
67 | // ctype := w.Header.Get("Content-Type") | ||
68 | // if req.Method != "GET" && ctype != "application/json" { | ||
69 | // replyWithHttpError(w, req, http.StatusBadRequest, | ||
70 | // "Not a supported content type: " + ctype) | ||
71 | // } | ||
72 | |||
73 | w.Header().Set("Access-Control-Allow-Origin", "*") | 57 | w.Header().Set("Access-Control-Allow-Origin", "*") |
58 | |||
74 | w.Header().Set("Access-Control-Allow-Methods", | 59 | w.Header().Set("Access-Control-Allow-Methods", |
75 | `POST, | 60 | "POST, GET, PUT, DELETE, OPTIONS") |
76 | GET, | 61 | |
77 | PUT, | ||
78 | DELETE, | ||
79 | OPTIONS`) | ||
80 | w.Header().Set("Access-Control-Allow-Headers", | 62 | w.Header().Set("Access-Control-Allow-Headers", |
81 | `Accept, | 63 | "Accept, Content-Type, Content-Length, " |
82 | Content-Type, | 64 | "Accept-Encoding, X-CSRF-Token, Authorization") |
83 | Content-Length, | 65 | |
84 | Accept-Encoding, | ||
85 | X-CSRF-Token, | ||
86 | Authorization`) | ||
87 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | 66 | w.Header().Set("Content-Type", "application/json; charset=utf-8") |
88 | 67 | ||
89 | if req.Method == "OPTIONS" { | 68 | if req.Method == "OPTIONS" { |
90 | return | 69 | return |
91 | } | 70 | } |
92 | 71 | ||
93 | if authEnabled { | 72 | if needauth { |
94 | if req.URL.Path != _apiVersion + _authEndPoint { | 73 | token := req.Header.Get("Authorization") |
95 | token := req.Header.Get("Authorization") | 74 | if _, err := ParseAPIToken(token); err != nil { |
96 | if _, err := ParseAPIToken(token); err != nil { | 75 | UnathorizedResponse(w, req, http.StatusUnauthorized) |
97 | RespondWithHttpError(w, req, http.StatusUnauthorized, | 76 | return |
98 | []HttpErrorDesc{ | ||
99 | {Lang: "en", Desc: "Unauthorized request."}, | ||
100 | {Lang: "rs", Desc: "Neautorizovani zahtev."}, | ||
101 | }) | ||
102 | return | ||
103 | } | ||
104 | } | 77 | } |
105 | } | 78 | } |
106 | 79 | ||
107 | err := req.ParseForm() | 80 | err := req.ParseForm() |
108 | if err != nil { | 81 | if err != nil { |
109 | RespondWithHttpError400(w, req) | 82 | BadRequestResponse(w, req) |
110 | return | 83 | return |
111 | } | 84 | } |
112 | 85 | ||
113 | // execute HandlerFunc | 86 | // execute HandlerFunc |
select_config.go
1 | package restutility | 1 | package restutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "gopkg.in/rana/ora.v3" | 4 | "gopkg.in/rana/ora.v3" |
5 | ) | 5 | ) |
6 | 6 | ||
7 | type SelectConfig struct { | 7 | type SelectConfig struct { |
8 | ListObjType string `json:"listObjectType"` | 8 | ListObjType string `json:"listObjectType"` |
9 | ObjType string `json:"objectType"` | 9 | ObjType string `json:"objectType"` |
10 | Type string `json:"type"` | 10 | Type string `json:"type"` |
11 | IdField string `json:"idField"` | 11 | IdField string `json:"idField"` |
12 | LabelField string `json:"labelField"` | 12 | LabelField string `json:"labelField"` |
13 | ValueField string `json:"valueField"` | 13 | ValueField string `json:"valueField"` |
14 | } | 14 | } |
15 | 15 | ||
16 | func GetSelectConfig(db *ora.Ses, otype string) ([]SelectConfig, error) { | 16 | func GetSelectConfig(db *ora.Ses, otype string) ([]SelectConfig, error) { |
17 | resp := make([]SelectConfig, 0) | 17 | resp := make([]SelectConfig, 0) |
18 | var err error | 18 | var err error |
19 | var stmt *ora.Stmt | 19 | var stmt *ora.Stmt |
20 | query := `SELECT a.LIST_OBJECT_TYPE, a.OBJECT_TYPE, a.ID_FIELD, | 20 | query := `SELECT a.LIST_OBJECT_TYPE, a.OBJECT_TYPE, a.ID_FIELD, |
21 | a.LABEL_FIELD, a.TYPE, b.FIELD | 21 | a.LABEL_FIELD, a.TYPE, b.FIELD |
22 | FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b | 22 | FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b |
23 | WHERE a.LIST_OBJECT_TYPE` + otype + ` | 23 | WHERE a.LIST_OBJECT_TYPE` + otype + ` |
24 | AND b.LIST_TYPE = a.LIST_OBJECT_TYPE | 24 | AND b.LIST_TYPE = a.LIST_OBJECT_TYPE |
25 | AND b.OBJECT_TYPE = a.OBJECT_TYPE` | 25 | AND b.OBJECT_TYPE = a.OBJECT_TYPE` |
26 | 26 | ||
27 | stmt, err = db.Prep(query, ora.S, ora.S, ora.S, ora.S, ora.S, | 27 | stmt, err = db.Prep(query, ora.S, ora.S, ora.S, ora.S, ora.S, ora.S) |
28 | ora.S) | ||
29 | defer stmt.Close() | 28 | defer stmt.Close() |
30 | if err != nil { | 29 | if err != nil { |
31 | return nil, err | 30 | return nil, err |
32 | } | 31 | } |
33 | 32 | ||
34 | rset, err := stmt.Qry() | 33 | rset, err := stmt.Qry() |
35 | if err != nil { | 34 | if err != nil { |
36 | return nil, err | 35 | return nil, err |
37 | } | 36 | } |
38 | for rset.Next() { | 37 | for rset.Next() { |
39 | resp = append(resp, SelectConfig{ | 38 | resp = append(resp, SelectConfig{ |
40 | ListObjType: rset.Row[0].(string), | 39 | ListObjType: rset.Row[0].(string), |
41 | ObjType: rset.Row[1].(string), | 40 | ObjType: rset.Row[1].(string), |
42 | IdField: rset.Row[2].(string), | 41 | IdField: rset.Row[2].(string), |
43 | LabelField: rset.Row[3].(string), | 42 | LabelField: rset.Row[3].(string), |
44 | Type: rset.Row[4].(string), | 43 | Type: rset.Row[4].(string), |
45 | ValueField: rset.Row[5].(string), | 44 | ValueField: rset.Row[5].(string), |
46 | }) | 45 | }) |
47 | } | 46 | } |
48 | if rset.Err != nil { | 47 | if rset.Err != nil { |
49 | return nil, rset.Err | 48 | return nil, rset.Err |
50 | } | 49 | } |
51 | 50 | ||
52 | return resp, nil | 51 | return resp, nil |
53 | |||
54 | } | 52 | } |
55 | 53 |