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  }