github.com/greenpau/go-authcrunch@v1.1.4/pkg/util/parser.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package util 16 17 import ( 18 "encoding/base64" 19 "fmt" 20 "net/http" 21 "strings" 22 ) 23 24 // ParseIdentity extracts user id/email and optional authentication realm 25 // from HTTP request. 26 func ParseIdentity(r *http.Request) (map[string]string, error) { 27 if r.Method == "POST" { 28 return parseIdentityForm(r) 29 } 30 return nil, fmt.Errorf("Request method %s is unsupported", r.Method) 31 } 32 33 // ParseCredentials extracts credentials from HTTP request. 34 func ParseCredentials(r *http.Request) (map[string]string, error) { 35 if r.Method == "POST" { 36 return parseAuthForm(r) 37 } 38 if r.Method == "GET" { 39 return parseAuthRequest(r) 40 } 41 return nil, fmt.Errorf("Request method %s is unsupported", r.Method) 42 } 43 44 func parseIdentityForm(r *http.Request) (map[string]string, error) { 45 var maxBytesLimit int64 = 500 46 var minBytesLimit int64 = 15 47 if r.ContentLength > maxBytesLimit { 48 return nil, fmt.Errorf("Request payload exceeded the limit of %d bytes: %d", maxBytesLimit, r.ContentLength) 49 } 50 if r.ContentLength < minBytesLimit { 51 return nil, fmt.Errorf("Request payload is too small: %d", r.ContentLength) 52 } 53 contentType := r.Header.Get("Content-Type") 54 if contentType != "application/x-www-form-urlencoded" { 55 return nil, fmt.Errorf("Request content type is not application/x-www-form-urlencoded") 56 } 57 58 rq := r.FormValue("activity") 59 if rq == "" { 60 rq = "login" 61 } 62 63 switch rq { 64 case "login": 65 default: 66 return nil, fmt.Errorf("request type is unsupported") 67 } 68 69 userID := r.FormValue("username") 70 realm := r.FormValue("realm") 71 if realm == "" { 72 realm = "local" 73 } 74 75 if (len(userID) < 2 || len(userID) > 100) || (len(realm) < 2 || len(realm) > 100) { 76 return nil, fmt.Errorf("received malformed request") 77 } 78 79 kv := map[string]string{ 80 "user": userID, 81 "realm": realm, 82 } 83 return kv, nil 84 } 85 86 func parseAuthForm(r *http.Request) (map[string]string, error) { 87 var reqFields []string 88 kv := make(map[string]string) 89 var maxBytesLimit int64 = 1000 90 var minBytesLimit int64 = 15 91 if r.ContentLength > maxBytesLimit { 92 return nil, fmt.Errorf("Request payload exceeded the limit of %d bytes: %d", maxBytesLimit, r.ContentLength) 93 } 94 if r.ContentLength < minBytesLimit { 95 return nil, fmt.Errorf("Request payload is too small: %d", r.ContentLength) 96 } 97 contentType := r.Header.Get("Content-Type") 98 if contentType != "application/x-www-form-urlencoded" { 99 return nil, fmt.Errorf("Request content type is not application/x-www-form-urlencoded") 100 } 101 102 rq := r.FormValue("activity") 103 if rq == "" { 104 rq = "login" 105 } 106 107 switch rq { 108 case "login": 109 reqFields = []string{"username", "password", "realm"} 110 default: 111 return nil, fmt.Errorf("request type is unsupported") 112 } 113 114 for _, k := range reqFields { 115 if v := r.FormValue(k); v != "" { 116 kv[k] = v 117 } 118 } 119 120 if _, exists := kv["realm"]; !exists { 121 kv["realm"] = "local" 122 } 123 124 return kv, nil 125 } 126 127 func parseAuthRequest(r *http.Request) (map[string]string, error) { 128 kv := make(map[string]string) 129 authzHeaderStr := r.Header.Get("Authorization") 130 if authzHeaderStr == "" { 131 return nil, nil 132 } 133 134 authzHeaderParts := strings.Split(authzHeaderStr, ",") 135 if len(authzHeaderParts) == 0 { 136 return nil, nil 137 } 138 139 authzStrParts := strings.Split(authzHeaderParts[0], " ") 140 if len(authzStrParts) != 2 { 141 return nil, nil 142 } 143 144 authzType := authzStrParts[0] 145 if authzType != "Basic" { 146 return nil, nil 147 } 148 authzStr, err := base64.StdEncoding.DecodeString(authzStrParts[1]) 149 if err != nil { 150 return nil, fmt.Errorf("decoding error: %s", err) 151 } 152 authzArr := strings.SplitN(string(authzStr), ":", 2) 153 if len(authzArr) != 2 { 154 return nil, fmt.Errorf("parsing error: %s", err) 155 } 156 kv["username"] = authzArr[0] 157 kv["password"] = authzArr[1] 158 if len(authzHeaderParts) == 1 { 159 kv["realm"] = "local" 160 return kv, nil 161 } 162 realmHeaderParts := strings.Split(authzHeaderParts[1], "=") 163 if len(realmHeaderParts) != 2 { 164 return nil, fmt.Errorf("realm parsing failed for %s", realmHeaderParts) 165 } 166 if realmHeaderParts[0] != "realm" { 167 return nil, fmt.Errorf("realm not found in %s", realmHeaderParts) 168 } 169 kv["realm"] = realmHeaderParts[1] 170 return kv, nil 171 }