diff --git a/pkg/common/common.go b/pkg/common/common.go deleted file mode 100644 index 0cbe4fef5a9ccb02b2973555bc42bf5c855cda80..0000000000000000000000000000000000000000 --- a/pkg/common/common.go +++ /dev/null @@ -1,109 +0,0 @@ -package common - -import ( - "bytes" - "crypto/rand" - "encoding/base64" - "encoding/json" - "io" - "net/http" - "os" - "path" - "sync" -) - -// Mutex used to lock file writing -var lock sync.Mutex - -// Save saves a representation of v to the file at path. -func Save(path string, v interface{}) error { - lock.Lock() - defer lock.Unlock() - f, err := os.Create(path) - if err != nil { - return err - } - defer f.Close() - r, err := Marshal(v) - if err != nil { - return err - } - _, err = io.Copy(f, r) - return err -} - -// Load loads the file at path into v. Use os.IsNotExist() to see if the returned error is due to the file being missing. -func Load(path string, v interface{}) error { - lock.Lock() - defer lock.Unlock() - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - return Unmarshal(f, v) -} - -// Marshal is a function that marshals the object into an io.Reader. By default, it uses the JSON marshaller. -var Marshal = func(v interface{}) (io.Reader, error) { - b, err := json.MarshalIndent(v, "", "\t") - if err != nil { - return nil, err - } - return bytes.NewReader(b), nil -} - -// Unmarshal is a function that unmarshals the data from the reader into the specified value. By default, it uses the JSON unmarshaller. -var Unmarshal = func(r io.Reader, v interface{}) error { - return json.NewDecoder(r).Decode(v) -} - -// GenerateRandomBytes returns securely generated random bytes. -// It will return an error if the system's secure random -// number generator fails to function correctly, in which -// case the caller should not continue. -func GenerateRandomBytes(n int) ([]byte, error) { - b := make([]byte, n) - _, err := rand.Read(b) - // Note that err == nil only if we read len(b) bytes. - if err != nil { - return nil, err - } - return b, nil -} - -// GenerateRandomString returns a URL-safe, base64 encoded -// securely generated random string. -// It will return an error if the system's secure random -// number generator fails to function correctly, in which -// case the caller should not continue. -func GenerateRandomString(s int) (string, error) { - b, err := GenerateRandomBytes(s) - return base64.URLEncoding.EncodeToString(b), err -} - -// FallBackWrapper serves a file if found and else default to index.html -type FallBackWrapper struct { - Assets http.FileSystem -} - -// Open serves a file if found and else default to index.html -func (i *FallBackWrapper) Open(name string) (http.File, error) { - file, err := i.Assets.Open(name) - // If the file is found but there is another error or the asked for file has an extension : return the file or error - if !os.IsNotExist(err) || path.Ext(name) != "" { - return file, err - } - // Else fall back to index.html - return i.Assets.Open("index.html") -} - -// Contains works out if a string slice contains a given string element -func Contains(a []string, x string) bool { - for _, n := range a { - if x == n { - return true - } - } - return false -} diff --git a/pkg/glob/LICENSE b/pkg/glob/LICENSE deleted file mode 100644 index bdfbd951497618c8cd39a592d40ec642ee7cb428..0000000000000000000000000000000000000000 --- a/pkg/glob/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Ryan Uber - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/pkg/glob/glob.go b/pkg/glob/glob.go deleted file mode 100644 index e67db3be183f75c09bda284b21123a1c6d0138f3..0000000000000000000000000000000000000000 --- a/pkg/glob/glob.go +++ /dev/null @@ -1,56 +0,0 @@ -package glob - -import "strings" - -// The character which is treated like a glob -const GLOB = "*" - -// Glob will test a string pattern, potentially containing globs, against a -// subject string. The result is a simple true/false, determining whether or -// not the glob pattern matched the subject text. -func Glob(pattern, subj string) bool { - // Empty pattern can only match empty subject - if pattern == "" { - return subj == pattern - } - - // If the pattern _is_ a glob, it matches everything - if pattern == GLOB { - return true - } - - parts := strings.Split(pattern, GLOB) - - if len(parts) == 1 { - // No globs in pattern, so test for equality - return subj == pattern - } - - leadingGlob := strings.HasPrefix(pattern, GLOB) - trailingGlob := strings.HasSuffix(pattern, GLOB) - end := len(parts) - 1 - - // Go over the leading parts and ensure they match. - for i := 0; i < end; i++ { - idx := strings.Index(subj, parts[i]) - - switch i { - case 0: - // Check the first section. Requires special handling. - if !leadingGlob && idx != 0 { - return false - } - default: - // Check that the middle parts match. - if idx < 0 { - return false - } - } - - // Trim evaluated text from subj as we loop over the pattern. - subj = subj[idx+len(parts[i]):] - } - - // Reached the last section. Requires special handling. - return trailingGlob || strings.HasSuffix(subj, parts[end]) -} diff --git a/pkg/glob/glob_test.go b/pkg/glob/glob_test.go deleted file mode 100644 index fa4edee2bb8565dcdf6d61efa3a5762d854c038c..0000000000000000000000000000000000000000 --- a/pkg/glob/glob_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package glob - -import ( - "strings" - "testing" -) - -func testGlobMatch(t *testing.T, pattern, subj string) { - if !Glob(pattern, subj) { - t.Fatalf("%s should match %s", pattern, subj) - } -} - -func testGlobNoMatch(t *testing.T, pattern, subj string) { - if Glob(pattern, subj) { - t.Fatalf("%s should not match %s", pattern, subj) - } -} - -func TestEmptyPattern(t *testing.T) { - testGlobMatch(t, "", "") - testGlobNoMatch(t, "", "test") -} - -func TestEmptySubject(t *testing.T) { - for _, pattern := range []string{ - "", - "*", - "**", - "***", - "****************", - strings.Repeat("*", 1000000), - } { - testGlobMatch(t, pattern, "") - } - - for _, pattern := range []string{ - // No globs/non-glob characters - "test", - "*test*", - - // Trailing characters - "*x", - "*****************x", - strings.Repeat("*", 1000000) + "x", - - // Leading characters - "x*", - "x*****************", - "x" + strings.Repeat("*", 1000000), - - // Mixed leading/trailing characters - "x*x", - "x****************x", - "x" + strings.Repeat("*", 1000000) + "x", - } { - testGlobNoMatch(t, pattern, "") - } -} - -func TestPatternWithoutGlobs(t *testing.T) { - testGlobMatch(t, "test", "test") -} - -func TestGlob(t *testing.T) { - // Matches - for _, pattern := range []string{ - "*test", // Leading glob - "this*", // Trailing glob - "this*test", // Middle glob - "*is *", // String in between two globs - "*is*a*", // Lots of globs - "**test**", // Double glob characters - "**is**a***test*", // Varying number of globs - "* *", // White space between globs - "*", // Lone glob - "**********", // Nothing but globs - "*Ѿ*", // Unicode with globs - "*is a ϗѾ *", // Mixed ASCII/unicode - } { - testGlobMatch(t, pattern, "this is a ϗѾ test") - } - - // Non-matches - for _, pattern := range []string{ - "test*", // Implicit substring match - "*is", // Partial match - "*no*", // Globs without a match between them - " ", // Plain white space - "* ", // Trailing white space - " *", // Leading white space - "*ʤ*", // Non-matching unicode - "this*this is a test", // Repeated prefix - } { - testGlobNoMatch(t, pattern, "this is a test") - } -} - -func BenchmarkGlob(b *testing.B) { - for i := 0; i < b.N; i++ { - if !Glob("*quick*fox*dog", "The quick brown fox jumped over the lazy dog") { - b.Fatalf("should match") - } - } -} diff --git a/pkg/log/log.go b/pkg/log/log.go deleted file mode 100644 index def58c67b407b7165dab80c1b5de847215566691..0000000000000000000000000000000000000000 --- a/pkg/log/log.go +++ /dev/null @@ -1,114 +0,0 @@ -package log - -import ( - "fmt" - "io" - "log" - "net" - "net/http" - "os" - "strings" - "sync" - "time" - - maxminddb "github.com/oschwald/maxminddb-golang" -) - -type cache struct { - mux sync.Mutex - last time.Time - content map[string]string -} - -// Logger represents a standard logger sets up for this application usage -var ( - Logger *log.Logger - ipcache = cache{ - last: time.Now(), - content: make(map[string]string), - } - ipDbLocation = "./configs/ipgeodatabase/GeoLite2-City.mmdb" - f *os.File -) - -func init() { - // Initialize logger - Logger = log.New(os.Stdout, "", log.LstdFlags) -} - -// SetFile set a file to log to instead of standard output -func SetFile(file string) { - var err error - f, err = os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) - if err != nil { - log.Fatalf("error opening file: %v", err) - } - wrt := io.MultiWriter(os.Stdout, f) - Logger.SetOutput(wrt) -} - -// CloseFile close the log file on exit -func CloseFile() { - f.Close() -} - -// GetCityAndCountryFromRequest returns a string containing the city and the contry where the request is from -// If the city is fetched from the built-in memory cache, it is NOT suffixed by a dot (.) -func GetCityAndCountryFromRequest(req *http.Request) string { - // If the request remote adress is local return "localhost" - if req.RemoteAddr == "" || strings.HasPrefix(req.RemoteAddr, "[::1]") || strings.HasPrefix(req.RemoteAddr, "127.0.0.1") { - return "localhost" - } - - // Lock the cache - ipcache.mux.Lock() - defer ipcache.mux.Unlock() - // Check if the cache is to old or to big - if time.Now().After(ipcache.last.Add(time.Hour*24)) || len(ipcache.content) > 1000 { - // If so reset the cache - ipcache.last = time.Now() - ipcache.content = make(map[string]string) - } - - // Get ip from remote address - address := strings.Split(req.RemoteAddr, ":")[0] - - // First check if the ip is in memory cache - locFromCache, ok := ipcache.content[address] - if ok { - return locFromCache - } - - // If not open the maxmind database, search the ip and update the cache - db, err := maxminddb.Open(ipDbLocation) - if err != nil { - Logger.Fatal(err) - } - defer db.Close() - - ip := net.ParseIP(address) - - if ip == nil { - return "ip could not be parsed" - } - - var record struct { - City struct { - Names map[string]string `maxminddb:"names"` - } `maxminddb:"city"` - Country struct { - Names map[string]string `maxminddb:"names"` - } `maxminddb:"country"` - } - - err = db.Lookup(ip, &record) - if err != nil { - Logger.Fatal(err) - } - if record.Country.Names["fr"] == "" { - return "ip not found" - } - ipFromDB := fmt.Sprintf("%v, %v", record.City.Names["fr"], record.Country.Names["fr"]) - ipcache.content[address] = ipFromDB - return ipFromDB + "." -} diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go deleted file mode 100644 index 54311c7738e542398bfb6cae3b3406f10f5c2315..0000000000000000000000000000000000000000 --- a/pkg/log/log_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package log - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -const londonIPAdress = "81.2.69.142:1234" - -func TestGetCityAndCountryFromRequest(t *testing.T) { - - ipDbLocation = "../../configs/ipgeodatabase/GeoLite2-City.mmdb" - - requestFromLocalHost := httptest.NewRequest("GET", "/test", strings.NewReader("")) - requestFromLocalHost.RemoteAddr = "[::1]:1234" - - requestFromLondon := httptest.NewRequest("GET", "/test", strings.NewReader("")) - requestFromLondon.RemoteAddr = londonIPAdress - - requestWithLocalIP := httptest.NewRequest("GET", "/test", strings.NewReader("")) - - type args struct { - req *http.Request - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Request from localhost", - args: args{ - req: requestFromLocalHost, - }, - want: "localhost", - }, - { - name: "Request from london", - args: args{ - req: requestFromLondon, - }, - want: "Londres, Royaume-Uni.", - }, - { - name: "Request from london, again", - args: args{ - req: requestFromLondon, - }, - want: "Londres, Royaume-Uni", - }, - { - name: "Request with empty ip", - args: args{ - req: requestWithLocalIP, - }, - want: "ip not found", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetCityAndCountryFromRequest(tt.args.req); got != tt.want { - t.Errorf("GetCityAndCountryFromRequest() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/log/middlewares.go b/pkg/log/middlewares.go deleted file mode 100644 index e1ae506ce3d7632fc4b09a02177a0865dc70ecf4..0000000000000000000000000000000000000000 --- a/pkg/log/middlewares.go +++ /dev/null @@ -1,26 +0,0 @@ -package log - -import ( - "bytes" - "io/ioutil" - "net/http" -) - -// Middleware allow extensive logging of requests for debug and development purposes only -func Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - readBody, err := ioutil.ReadAll(r.Body) - if err != nil { - Logger.Print("Body error : ", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - newBody := ioutil.NopCloser(bytes.NewBuffer(readBody)) - r.Body = newBody - Logger.Println(r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent()) - if string(readBody) != "" { - Logger.Printf("BODY : %q", readBody) - } - next.ServeHTTP(w, r) - }) -} diff --git a/pkg/middlewares/middlewares.go b/pkg/middlewares/middlewares.go deleted file mode 100644 index 0694f69f7291fb73e3d93f74cd6dc0a05d466c3f..0000000000000000000000000000000000000000 --- a/pkg/middlewares/middlewares.go +++ /dev/null @@ -1,51 +0,0 @@ -package middlewares - -import ( - "fmt" - "net/http" - "strconv" -) - -// Cors enables CORS Request on server (for development purposes) -func Cors(next http.Handler, allowedOrigin string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", allowedOrigin) - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PROPFIND, MKCOL, MOVE, COPY") - w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, XSRF-TOKEN, Authorization, Depth, Destination") - w.Header().Set("Access-Control-Allow-Credentials", "true") - next.ServeHTTP(w, req) - }) -} - -// WebSecurity adds good practices security headers on http responses -func WebSecurity(next http.Handler, source string, allowEvalInlineScript bool) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Strict-Transport-Security", "max-age=63072000") - var inline string - if allowEvalInlineScript { - inline = "'unsafe-inline' 'unsafe-eval'" - } - w.Header().Set("Content-Security-Policy", fmt.Sprintf("default-src %[1]v 'self'; img-src %[1]v blob: 'self'; script-src 'self' %[1]v %[2]v; style-src 'self' 'unsafe-inline'; frame-src %[1]v; frame-ancestors %[1]v", source, inline)) - //w.Header().Set("X-Frame-Options", "SAMEORIGIN") // Works fine with chrome but is not obsoleted by frame-src in firefox 72.0.2 - w.Header().Set("X-XSS-Protection", "1; mode=block") - w.Header().Set("Referrer-Policy", "strict-origin") - w.Header().Set("X-Content-Type-Options", "nosniff") - next.ServeHTTP(w, req) - }) -} - -// NoCache disable caching -func NoCache(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Cache-Control", "no-store, must-revalidate") - next.ServeHTTP(w, req) - }) -} - -// GetFullHostname returns the full hostname of the server -func GetFullHostname(hostname string, port int) string { - if port == 80 || port == 443 { - return "https://" + hostname - } - return "https://" + hostname + ":" + strconv.Itoa(port) -} diff --git a/pkg/tester/tester.go b/pkg/tester/tester.go deleted file mode 100644 index 11f7daae9bfcb4313425aa9b23dd7fee286659c4..0000000000000000000000000000000000000000 --- a/pkg/tester/tester.go +++ /dev/null @@ -1,96 +0,0 @@ -package tester - -import ( - "context" - "io/ioutil" - "net" - "net/http" - "net/http/cookiejar" - "net/http/httptest" - "net/url" - "strings" - "testing" - "time" -) - -//Header is a http header -type Header struct { - Key string - Value string -} - -// DoRequestOnHandler does a request on a router (or handler) and check the response -func DoRequestOnHandler(t *testing.T, router http.Handler, method string, route string, header Header, payload string, expectedStatus int, expectedBody string) string { - req, err := http.NewRequest(method, route, strings.NewReader(payload)) - if err != nil { - t.Fatal(err) - } - if header.Key != "" { - req.Header.Set(header.Key, header.Value) - } - rr := httptest.NewRecorder() - router.ServeHTTP(rr, req) - if status := rr.Code; status != expectedStatus { - t.Errorf("Tested %v %v %v ; handler returned wrong status code: got %v want %v", method, route, payload, status, expectedStatus) - } - if !strings.HasPrefix(rr.Body.String(), expectedBody) { - t.Errorf("Tested %v %v %v ; handler returned unexpected body: got %v want %v", method, route, payload, rr.Body.String(), expectedBody) - } - return string(rr.Body.String()) -} - -// DoRequestOnServer does a request on listening server -func DoRequestOnServer(t *testing.T, hostname string, port string, jar *cookiejar.Jar, method string, testURL string, header Header, payload string, expectedStatus int, expectedBody string) string { - dialer := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - } - // or create your own transport, there's an example on godoc. - http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - addrAndPort := strings.Split(addr, ":") - if strings.HasSuffix(addrAndPort[0], "sdk-go.io") { - addr = "127.0.0.1:" + addrAndPort[1] - } - return dialer.DialContext(ctx, network, addr) - } - if strings.HasPrefix(testURL, "/") { - testURL = "http://" + hostname + ":" + port + testURL - } else { - u, _ := url.Parse("http://" + testURL) - testURL = "http://" + u.Host + ":" + port + u.Path + "?" + u.RawQuery - } - req, err := http.NewRequest(method, testURL, strings.NewReader(payload)) - if err != nil { - t.Fatal(err) - } - if header.Key != "" { - req.Header.Set(header.Key, header.Value) - } - var client *http.Client - if jar != nil { - client = &http.Client{Jar: jar} - } else { - client = &http.Client{} - } - res, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - body, _ := ioutil.ReadAll(res.Body) - bodyString := string(body) - if status := res.StatusCode; status != expectedStatus { - t.Errorf("Tested %v %v %v ; handler returned wrong status code: got %v want %v", method, testURL, payload, status, expectedStatus) - } - if !strings.HasPrefix(bodyString, expectedBody) { - t.Errorf("Tested %v %v %v ; handler returned unexpected body: got %v want %v", method, testURL, payload, bodyString, expectedBody) - } - return bodyString -} - -// CreateServerTester wraps DoRequestOnServer to factorize t, port and jar -func CreateServerTester(t *testing.T, hostname string, port string, jar *cookiejar.Jar) func(method string, url string, header Header, payload string, expectedStatus int, expectedBody string) string { - return func(method string, url string, header Header, payload string, expectedStatus int, expectedBody string) string { - return DoRequestOnServer(t, port, hostname, jar, method, url, header, payload, expectedStatus, expectedBody) - } -} diff --git a/pkg/tokens/tokens.go b/pkg/tokens/tokens.go deleted file mode 100644 index 550dff67ae6af91c18c36ff7fb4eeca7491a1398..0000000000000000000000000000000000000000 --- a/pkg/tokens/tokens.go +++ /dev/null @@ -1,223 +0,0 @@ -package tokens - -import ( - "bytes" - "compress/flate" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "strings" - "time" - - "forge.grandlyon.com/apoyen/sdk-go/pkg/common" - "forge.grandlyon.com/apoyen/sdk-go/pkg/log" -) - -var ( - now = time.Now - // Manager is the current token manager - Manager manager -) - -// manager manages tokens -type manager struct { - key []byte - debugMode bool -} - -// Init inits the main token manager -func Init(keyfile string, debug bool) { - Manager = newManager(keyfile, debug) -} - -// newManager creates a manager -func newManager(keyfile string, debug bool) manager { - var keyConfig struct { - Key []byte - } - err := common.Load(keyfile, &keyConfig) - if err != nil { - keyConfig.Key, err = common.GenerateRandomBytes(32) - if err != nil { - log.Logger.Fatal(err) - } - err := common.Save(keyfile, keyConfig) - if err != nil { - log.Logger.Println("Token signing key could not be saved") - } - } - log.Logger.Println("Token signing key set") - return manager{ - debugMode: debug, - key: keyConfig.Key, - } -} - -// Token represents a token containting data -type Token struct { - ExpiresAt int64 - IssuedAt int64 `json:"iat,omitempty"` - Data []byte -} - -// StoreData creates a token with the given data and returns it in a cookie -func (m manager) StoreData(data interface{}, hostName string, cookieName string, duration time.Duration, w http.ResponseWriter) { - expiration := now().Add(duration) - value, err := m.CreateToken(data, expiration) - if err != nil { - http.Error(w, err.Error(), 500) - return - } - cookie := http.Cookie{Name: cookieName, Domain: hostName, Value: value, Expires: expiration, Secure: !m.debugMode, HttpOnly: true, SameSite: http.SameSiteLaxMode} - http.SetCookie(w, &cookie) -} - -// CreateToken creates a token with the given data -func (m manager) CreateToken(data interface{}, expiration time.Time) (string, error) { - // Marshall the data - d, err := json.Marshal(data) - if err != nil { - return "", err - } - // Create the payload - token := Token{ - ExpiresAt: expiration.Unix(), - Data: d, - } - // Serialize the payload - sToken, err := json.Marshal(token) - if err != nil { - return "", err - } - // Compress with deflate - var csToken bytes.Buffer - c, err := flate.NewWriter(&csToken, flate.BestCompression) - if _, err := c.Write(sToken); err != nil { - return "", err - } - if err := c.Close(); err != nil { - return "", err - } - ecsToken, err := Encrypt(csToken.Bytes(), m.key) - if err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(ecsToken), nil -} - -// ExtractAndValidateToken extracts the token from the request, validates it, and return the data n the value pointed to by v -func (m manager) ExtractAndValidateToken(r *http.Request, cookieName string, v interface{}, checkXSRF bool) (bool, error) { - becsToken, checkXSRF, err := func(r *http.Request, checkXSRF bool) (string, bool, error) { - // Try to extract from the query - query := r.URL.Query().Get("token") - if query != "" { - return query, false, nil - } - // Try to extract from the cookie - cookie, err := r.Cookie(cookieName) - if err == nil { - return cookie.Value, checkXSRF, err - } - // Try to get an auth token from the header - authHeader := strings.Split(r.Header.Get("Authorization"), " ") - if authHeader[0] == "Bearer" && len(authHeader) == 2 { - return authHeader[1], false, nil - } - // Try to use the basic auth header instead - if authHeader[0] == "Basic" && len(authHeader) == 2 { - decoded, err := base64.StdEncoding.DecodeString(authHeader[1]) - if err == nil { - authHeader = strings.Split(string(decoded), ":") - return authHeader[1], false, nil - } - } - return "", false, errors.New("could not extract token") - }(r, checkXSRF) - - if err == nil { - return checkXSRF, m.unstoreData(becsToken, v) - } - return false, err -} - -// unstoreData decrypt, uncompress, unserialize the token, and returns the data n the value pointed to by v -func (m manager) unstoreData(becsToken string, v interface{}) error { - // Decrypt the token - ecsToken, err := base64.StdEncoding.DecodeString(becsToken) - if err != nil { - return fmt.Errorf("failed to unbase64 token") - - } - csToken, err := Decrypt(ecsToken, m.key) - if err != nil { - return fmt.Errorf("failed to decrypt token") - - } - // Uncompress the token - rdata := bytes.NewReader(csToken) - r := flate.NewReader(rdata) - sToken, err := ioutil.ReadAll(r) - if err != nil { - return fmt.Errorf("failed to uncompress token") - - } - // Unserialize the token - token := Token{} - err = json.Unmarshal(sToken, &token) - if err != nil { - return fmt.Errorf("failed to unmarshall token") - - } - // Validate the token - if token.ExpiresAt < now().Unix() { - return fmt.Errorf("token expired") - } - // Update the data - err = json.Unmarshal(token.Data, v) - // Return no error if everything is fine - return nil -} - -// Encrypt a byte array with AES -func Encrypt(data []byte, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return []byte{}, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return []byte{}, err - } - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return []byte{}, err - } - cipherData := gcm.Seal(nonce, nonce, data, nil) - return cipherData, nil -} - -// Decrypt a byte array with AES -func Decrypt(data []byte, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return []byte{}, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return []byte{}, err - } - nonceSize := gcm.NonceSize() - nonce, cipherData := data[:nonceSize], data[nonceSize:] - plainData, err := gcm.Open(nil, nonce, cipherData, nil) - if err != nil { - return []byte{}, err - } - return plainData, nil -} diff --git a/pkg/tokens/tokens_test.go b/pkg/tokens/tokens_test.go deleted file mode 100644 index dc7b6307adbbcaec9e4321ee8562e6ab524530d1..0000000000000000000000000000000000000000 --- a/pkg/tokens/tokens_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package tokens - -import ( - "fmt" - "testing" - "time" - - "forge.grandlyon.com/apoyen/sdk-go/pkg/common" -) - -type user struct { - Login string - Password string -} - -func (u user) String() string { - return fmt.Sprintf("Login: %v, Password: %v", u.Login, u.Password) -} - -func Test_manager_CreateToken_unStoreData(t *testing.T) { - key, _ := common.GenerateRandomBytes(32) - key2, _ := common.GenerateRandomBytes(32) - type fields struct { - encryptKey []byte - decryptKey []byte - debugMode bool - } - type args struct { - data interface{} - expiration time.Time - } - tests := []struct { - name string - fields fields - args args - want bool - wantErr bool - }{ - {"future_expiration", fields{key, key, false}, args{user{"admin", "password"}, time.Now().Add(24 * time.Hour)}, true, false}, - {"past_expiration", fields{key, key, false}, args{user{"admin", "password"}, time.Now().Add(-24 * time.Hour)}, false, true}, - {"incorrect_aes_key", fields{[]byte("wrong_key_size"), []byte("wrong_key_size"), false}, args{user{"admin", "password"}, time.Now().Add(+24 * time.Hour)}, false, true}, - {"wrong_decrypt_key", fields{key, key2, false}, args{user{"admin", "password"}, time.Now().Add(+24 * time.Hour)}, false, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := manager{ - key: tt.fields.encryptKey, - debugMode: tt.fields.debugMode, - } - token, _ := m.CreateToken(tt.args.data, tt.args.expiration) - m.key = tt.fields.decryptKey - v := user{} - err := m.unstoreData(token, &v) - got := tt.args.data == v - if (err != nil) != tt.wantErr { - t.Errorf("manager.(un)storeData() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("manager.(un)storeData() inData:%v, outData:%v => equality: %v, want %v", tt.args.data, v, got, tt.want) - } - }) - } -}