github.com/minio/console@v1.3.0/sso-integration/sso_test.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package ssointegration 18 19 import ( 20 "bytes" 21 "encoding/base64" 22 "encoding/json" 23 "fmt" 24 "io" 25 "log" 26 "net/http" 27 "net/url" 28 "os/exec" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/minio/console/models" 34 35 "github.com/go-openapi/loads" 36 "github.com/minio/console/api" 37 "github.com/minio/console/api/operations" 38 consoleoauth2 "github.com/minio/console/pkg/auth/idp/oauth2" 39 "github.com/stretchr/testify/assert" 40 ) 41 42 var token string 43 44 func initConsoleServer(consoleIDPURL string) (*api.Server, error) { 45 // Configure Console Server with vars to get the idp config from the container 46 pcfg := map[string]consoleoauth2.ProviderConfig{ 47 "_": { 48 URL: consoleIDPURL, 49 ClientID: "minio-client-app", 50 ClientSecret: "minio-client-app-secret", 51 RedirectCallback: "http://127.0.0.1:9090/oauth_callback", 52 }, 53 } 54 55 swaggerSpec, err := loads.Embedded(api.SwaggerJSON, api.FlatSwaggerJSON) 56 if err != nil { 57 return nil, err 58 } 59 60 noLog := func(string, ...interface{}) { 61 // nothing to log 62 } 63 64 // Initialize MinIO loggers 65 api.LogInfo = noLog 66 api.LogError = noLog 67 68 consoleAPI := operations.NewConsoleAPI(swaggerSpec) 69 consoleAPI.Logger = noLog 70 71 api.GlobalMinIOConfig = api.MinIOConfig{ 72 OpenIDProviders: pcfg, 73 } 74 75 server := api.NewServer(consoleAPI) 76 // register all APIs 77 server.ConfigureAPI() 78 79 server.Host = "0.0.0.0" 80 server.Port = 9090 81 api.Port = "9090" 82 api.Hostname = "0.0.0.0" 83 84 return server, nil 85 } 86 87 func TestMain(t *testing.T) { 88 assert := assert.New(t) 89 90 // start console server 91 go func() { 92 fmt.Println("start server") 93 srv, err := initConsoleServer("http://dex:5556/dex/.well-known/openid-configuration") 94 if err != nil { 95 log.Println(err) 96 log.Println("init fail") 97 return 98 } 99 srv.Serve() 100 }() 101 102 fmt.Println("sleeping") 103 time.Sleep(2 * time.Second) 104 105 client := &http.Client{ 106 Timeout: 2 * time.Second, 107 } 108 109 // Let's move this API here to increment our coverage 110 getRequest, getError := http.NewRequest("GET", "http://localhost:9090/api/v1/login", nil) 111 if getError != nil { 112 log.Println(getError) 113 return 114 } 115 getRequest.Header.Add("Content-Type", "application/json") 116 getResponse, getErr := client.Do(getRequest) 117 // current value: 118 // {"loginStrategy":"form"} 119 // but we want our console server to provide loginStrategy = redirect for SSO 120 if getErr != nil { 121 log.Println(getErr) 122 return 123 } 124 125 body, err := io.ReadAll(getResponse.Body) 126 getResponse.Body.Close() 127 if getResponse.StatusCode > 299 { 128 log.Fatalf("Response failed with status code: %d and\nbody: %s\n", getResponse.StatusCode, body) 129 } 130 if err != nil { 131 log.Fatal(err) 132 } 133 var jsonMap models.LoginDetails 134 135 fmt.Println(body) 136 137 err = json.Unmarshal(body, &jsonMap) 138 if err != nil { 139 fmt.Printf("error JSON Unmarshal %s\n", err) 140 } 141 142 redirectRule := jsonMap.RedirectRules[0] 143 redirectAsString := fmt.Sprint(redirectRule.Redirect) 144 fmt.Println(redirectAsString) 145 146 // execute script to get the code and state 147 cmd, err := exec.Command("python3", "dex-requests.py", redirectAsString).Output() 148 if err != nil { 149 fmt.Printf("error %s\n", err) 150 } 151 urlOutput := string(cmd) 152 fmt.Println("url output:", urlOutput) 153 requestLoginBody := bytes.NewReader([]byte("login=dillon%40example.io&password=dillon")) 154 155 // parse url remove carriage return 156 temp2 := strings.Split(urlOutput, "\n") 157 fmt.Println("temp2: ", temp2) 158 urlOutput = temp2[0] // remove carriage return to avoid invalid control character in url 159 160 // validate url 161 urlParseResult, urlParseError := url.Parse(urlOutput) 162 if urlParseError != nil { 163 panic(urlParseError) 164 } 165 fmt.Println(urlParseResult) 166 167 // prepare for post 168 httpRequestLogin, newRequestError := http.NewRequest( 169 "POST", 170 urlOutput, 171 requestLoginBody, 172 ) 173 if newRequestError != nil { 174 fmt.Println(newRequestError) 175 } 176 httpRequestLogin.Header.Add("Content-Type", "application/x-www-form-urlencoded") 177 responseLogin, errorLogin := client.Do(httpRequestLogin) 178 if errorLogin != nil { 179 log.Println(errorLogin) 180 } 181 rawQuery := responseLogin.Request.URL.RawQuery 182 fmt.Println(rawQuery) 183 splitRawQuery := strings.Split(rawQuery, "&state=") 184 codeValue := strings.ReplaceAll(splitRawQuery[0], "code=", "") 185 stateValue := splitRawQuery[1] 186 fmt.Println("stop", splitRawQuery, codeValue, stateValue) 187 188 // get login credentials 189 codeVarIable := strings.TrimSpace(codeValue) 190 stateVarIabl := strings.TrimSpace(stateValue) 191 requestData := map[string]string{ 192 "code": codeVarIable, 193 "state": stateVarIabl, 194 } 195 requestDataJSON, _ := json.Marshal(requestData) 196 197 requestDataBody := bytes.NewReader(requestDataJSON) 198 199 request, _ := http.NewRequest( 200 "POST", 201 "http://localhost:9090/api/v1/login/oauth2/auth", 202 requestDataBody, 203 ) 204 request.Header.Add("Content-Type", "application/json") 205 206 response, err := client.Do(request) 207 if err != nil { 208 log.Println(err) 209 } 210 if response != nil { 211 for _, cookie := range response.Cookies() { 212 if cookie.Name == "token" { 213 token = cookie.Value 214 break 215 } 216 } 217 } 218 fmt.Println(response.Status) 219 if token == "" { 220 assert.Fail("authentication token not found in cookies response") 221 } else { 222 fmt.Println(token) 223 } 224 } 225 226 func TestBadLogin(t *testing.T) { 227 assert := assert.New(t) 228 229 // start console server 230 go func() { 231 fmt.Println("start server") 232 srv, err := initConsoleServer("http://dex:5556") 233 if err != nil { 234 log.Println(err) 235 log.Println("init fail") 236 return 237 } 238 srv.Serve() 239 }() 240 fmt.Println("sleeping") 241 time.Sleep(2 * time.Second) 242 243 client := &http.Client{ 244 Timeout: 2 * time.Second, 245 } 246 247 encodeItem := consoleoauth2.LoginURLParams{ 248 State: "invalidState", 249 IDPName: "_", 250 } 251 252 jsonState, err := json.Marshal(encodeItem) 253 if err != nil { 254 log.Println(err) 255 assert.Nil(err) 256 } 257 258 // get login credentials 259 stateVarIable := base64.StdEncoding.EncodeToString(jsonState) 260 261 codeVarIable := "invalidCode" 262 263 requestData := map[string]string{ 264 "code": codeVarIable, 265 "state": stateVarIable, 266 } 267 requestDataJSON, _ := json.Marshal(requestData) 268 269 requestDataBody := bytes.NewReader(requestDataJSON) 270 271 request, _ := http.NewRequest( 272 "POST", 273 "http://localhost:9090/api/v1/login/oauth2/auth", 274 requestDataBody, 275 ) 276 request.Header.Add("Content-Type", "application/json") 277 278 response, err := client.Do(request) 279 fmt.Println(response) 280 fmt.Println(err) 281 expectedError := response.Status 282 assert.Equal("400 Bad Request", expectedError) 283 bodyBytes, _ := io.ReadAll(response.Body) 284 result2 := models.APIError{} 285 err = json.Unmarshal(bodyBytes, &result2) 286 if err != nil { 287 log.Println(err) 288 assert.Nil(err) 289 } 290 }