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 }