github.com/SUSE/skuba@v1.4.17/pkg/skuba/actions/auth/testing.go (about)

     1  /*
     2   * Copyright (c) 2019 SUSE LLC. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package auth
    19  
    20  import (
    21  	"crypto/rand"
    22  	"encoding/base32"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"strconv"
    28  	"strings"
    29  )
    30  
    31  const (
    32  	mockDefaultUsername = "hello@suse.com"
    33  	mockDefaultPassword = "bar"
    34  
    35  	mockIDToken      = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImFlNjg5YTI1OWJkYjRjMWZiZDZmZGFjMzg0OTk5YTJhNWNlNmRmOGEifQ.eyJpc3MiOiJodHRwczovLzEwLjg2LjAuMTE2OjMyMDAwIiwic3ViIjoiQ2loamJqMW9aV3hzYjNkdmNteGtMRzkxUFhWelpYSnpMR1JqUFdWNFlXMXdiR1VzWkdNOWIzSm5FZ1JzWkdGdyIsImF1ZCI6WyJvaWRjIiwib2lkYy1jbGkiXSwiZXhwIjoxNTY1MTY1NjE0LCJpYXQiOjE1NjUwNzkyMTQsImF6cCI6Im9pZGMtY2xpIiwiYXRfaGFzaCI6ImJ2cml3TEthMzZuTjExUG5Jc3RRSmciLCJlbWFpbCI6ImhlbGxvQHN1c2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImdyb3VwcyI6WyJkZXZlbG9wZXJzIl0sIm5hbWUiOiJIZWxsbyBXb3JsZCJ9.AYN9cbk2hS6S8ZbQSZ4yoGksPJJ9qzbK8iXCoB6XXmhc5AUlwxnXQ-vzcp1u6h8AtY3iJX0s5ZwH3BthKEBlj6Aad6v5qp62Ws0Wb1-RY6TcCNQv4AdpBuFlJtJIxp7wI33bR0gpLOMsjYJRgKuLvQ1Dn7tipT62CPhqwA91lT613_yByLC8ek1Qy3RSwJIA_hkJT0H-yMHM2JC5WuB3P0MEURfl2QIXaWDjoV5RcL0dh_dkwy2v6zxgCPu0gFvL2BOrcHPjv6k6kphMnQ8uCbQaEfNxuMYr7zDRWBcNSpfjhbbYRAjNBHbpMorM3mT83GB76cxdUWCW2q69nM1B_w"
    36  	mockRefreshToken = "ChludG1ncnh1aHQ1a3F0dG83enRvYmtlc2hiEhlsNWNvM3V6cmVjb2FxYW1maHZqa2F5azJh"
    37  )
    38  
    39  var (
    40  	localhostCert = []byte(`-----BEGIN CERTIFICATE-----
    41  MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
    42  MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
    43  MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
    44  iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
    45  iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
    46  rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
    47  BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
    48  AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
    49  AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
    50  tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
    51  h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
    52  fblo6RBxUQ==
    53  -----END CERTIFICATE-----
    54  `)
    55  )
    56  
    57  func newID() string {
    58  	var encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
    59  
    60  	buff := make([]byte, 16) // 128 bit random ID.
    61  	if _, err := io.ReadFull(rand.Reader, buff); err != nil {
    62  		panic(err)
    63  	}
    64  	// Avoid the identifier to begin with number and trim padding
    65  	return string(buff[0]%26+'a') + strings.TrimRight(encoding.EncodeToString(buff[1:]), "=")
    66  }
    67  
    68  func openIDHandler() func(w http.ResponseWriter, r *http.Request) {
    69  	return func(w http.ResponseWriter, r *http.Request) {
    70  		url := fmt.Sprintf("%s://%s", defaultScheme, r.Host)
    71  
    72  		_ = json.NewEncoder(w).Encode(&map[string]interface{}{
    73  			"issuer":                                url,
    74  			"authorization_endpoint":                fmt.Sprintf("%s/auth", url),
    75  			"token_endpoint":                        fmt.Sprintf("%s/token", url),
    76  			"jwks_uri":                              fmt.Sprintf("%s/keys", url),
    77  			"response_types_supported":              []string{"code"},
    78  			"subject_types_supported":               []string{"public"},
    79  			"id_token_signing_alg_values_supported": []string{"RS256"},
    80  			"scopes_supported":                      []string{"openid", "email", "groups", "profile", "offline_access"},
    81  			"token_endpoint_auth_methods_supported": []string{"client_secret_basic"},
    82  			"claims_supported":                      []string{"aud", "email", "email_verified", "exp", "iat", "iss", "locale", "name", "sub"},
    83  		})
    84  	}
    85  }
    86  
    87  func openIDHandlerInvalidScopes() func(w http.ResponseWriter, r *http.Request) {
    88  	return func(w http.ResponseWriter, r *http.Request) {
    89  		url := fmt.Sprintf("%s://%s", defaultScheme, r.Host)
    90  
    91  		_ = json.NewEncoder(w).Encode(&map[string]interface{}{
    92  			"issuer":           url,
    93  			"scopes_supported": []int{1, 2, 3},
    94  		})
    95  	}
    96  }
    97  
    98  func openIDHandlerNoScopes() func(w http.ResponseWriter, r *http.Request) {
    99  	return func(w http.ResponseWriter, r *http.Request) {
   100  		url := fmt.Sprintf("%s://%s", defaultScheme, r.Host)
   101  
   102  		_ = json.NewEncoder(w).Encode(&map[string]interface{}{
   103  			"issuer": url,
   104  		})
   105  	}
   106  }
   107  
   108  func authSingleConnectorHandler() func(w http.ResponseWriter, r *http.Request) {
   109  	return func(w http.ResponseWriter, r *http.Request) {
   110  		url := fmt.Sprintf("%s://%s", defaultScheme, r.Host)
   111  
   112  		http.Redirect(w, r, fmt.Sprintf("%s/auth/local", url)+"?req="+newID(), http.StatusFound)
   113  	}
   114  }
   115  
   116  func authMultipleConnectorsHandler() func(w http.ResponseWriter, r *http.Request) {
   117  	return func(w http.ResponseWriter, r *http.Request) {
   118  		htmlOutput := fmt.Sprintf(`
   119  		<!DOCTYPE html>
   120  		<html>
   121    		<head>
   122      		<title>SUSE CaaS Platform</title>
   123    		</head>
   124    		<body class="theme-body">
   125      		<div class="dex-container">
   126  			<div class="theme-panel">
   127    				<h2 class="theme-heading">Log in to SUSE CaaS Platform </h2>
   128    				<div>
   129  					<div class="theme-form-row">
   130  						<a href="/auth/local?req=ocg6tiqn525sadbm2ul6h77mk" target="_self">
   131  							<button class="dex-btn theme-btn-provider">
   132  								<span class="dex-btn-icon dex-btn-icon--local"></span>
   133  								<span class="dex-btn-text">Log in with Email</span>
   134  							</button>
   135  						</a>
   136  					</div>
   137  					<div class="theme-form-row">
   138  						<a href="/auth/ldap?req=ocg6tiqn525sadbm2ul6h77mk" target="_self">
   139  							<button class="dex-btn theme-btn-provider">
   140  								<span class="dex-btn-icon dex-btn-icon--ldap"></span>
   141  								<span class="dex-btn-text">Log in with openLDAP</span>
   142  							</button>
   143  						</a>
   144  					</div>
   145    				</div>
   146  			</div>
   147      		</div>
   148  		</body>
   149  		</html>		
   150  		`)
   151  		_, _ = w.Write([]byte(htmlOutput))
   152  	}
   153  }
   154  
   155  func authLocalHandler() func(w http.ResponseWriter, r *http.Request) {
   156  	return func(w http.ResponseWriter, r *http.Request) {
   157  		url := fmt.Sprintf("%s://%s", defaultScheme, r.Host)
   158  		authReqID := r.FormValue("req")
   159  
   160  		switch r.Method {
   161  		case http.MethodGet:
   162  			htmlOutput := fmt.Sprintf(`
   163  				<div class="theme-panel">
   164  				<h2 class="theme-heading">Log in to Your Account</h2>
   165  				<form method="post" action="/auth/local?req=%s">
   166  					<div class="theme-form-row">
   167  					<div class="theme-form-label">
   168  						<label for="userid">Email Address</label>
   169  					</div>
   170  					<input tabindex="1" required id="login" name="login" type="text" class="theme-form-input" placeholder="email address"  autofocus />
   171  					</div>
   172  					<div class="theme-form-row">
   173  					<div class="theme-form-label">
   174  						<label for="password">Password</label>
   175  						</div>
   176  					<input tabindex="2" required id="password" name="password" type="password" class="theme-form-input" placeholder="password" />
   177  					</div>
   178  					<button tabindex="3" id="submit-login" type="submit" class="dex-btn theme-btn--primary">Login</button>
   179  				</form>
   180  				</div>
   181  				`, authReqID)
   182  			_, _ = w.Write([]byte(htmlOutput))
   183  		case http.MethodPost:
   184  			username := r.FormValue("login")
   185  			password := r.FormValue("password")
   186  			if username == mockDefaultUsername && password == mockDefaultPassword {
   187  				http.Redirect(w, r, fmt.Sprintf("%s/approval", url)+"?req="+authReqID, http.StatusSeeOther)
   188  			}
   189  		}
   190  	}
   191  }
   192  
   193  func tokenHandler() func(w http.ResponseWriter, r *http.Request) {
   194  	return func(w http.ResponseWriter, r *http.Request) {
   195  		resp := struct {
   196  			AccessToken  string `json:"access_token"`
   197  			TokenType    string `json:"token_type"`
   198  			ExpiresIn    int    `json:"expires_in"`
   199  			RefreshToken string `json:"refresh_token,omitempty"`
   200  			IDToken      string `json:"id_token"`
   201  		}{
   202  			newID(),
   203  			"bearer",
   204  			86399,
   205  			mockRefreshToken,
   206  			mockIDToken,
   207  		}
   208  		data, _ := json.Marshal(resp)
   209  		w.Header().Set("Content-Type", "application/json")
   210  		w.Header().Set("Content-Length", strconv.Itoa(len(data)))
   211  		_, _ = w.Write(data)
   212  	}
   213  }
   214  
   215  func approvalHandler() func(w http.ResponseWriter, r *http.Request) {
   216  	return func(w http.ResponseWriter, r *http.Request) {
   217  		htmlOutput := fmt.Sprintf(`
   218  			<div class="theme-panel">
   219  			<h2 class="theme-heading">Login Successful</h2>
   220  			<p>Please copy this code, switch to your application and paste it there:</p>
   221  			<input type="text" class="theme-form-input" value="%s" />
   222  			</div>
   223  		`, newID())
   224  		_, _ = w.Write([]byte(htmlOutput))
   225  	}
   226  }
   227  
   228  func approvalInvalidBodyHandler() func(w http.ResponseWriter, r *http.Request) {
   229  	return func(w http.ResponseWriter, r *http.Request) {
   230  		htmlOutput := fmt.Sprintf(`
   231  			<div class="theme-panel">
   232  			<h2 class="theme-heading">Login Successful</h2>
   233  			</div>
   234  		`)
   235  		_, _ = w.Write([]byte(htmlOutput))
   236  	}
   237  }