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  }