/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package oidc_test import ( "bytes" "context" "io/ioutil" "math/rand" "net/http" "net/url" "strings" "time" "github.com/Nerzal/gocloak/v11" "github.com/PuerkitoBio/goquery" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Oidc-Login", func() { var ( OidcCookie []http.Cookie ) Context("test apisix/admin/oidc/login", func() { It("should return status-code 302", func() { statusCode, err := accessOidcLogin() Expect(err).ShouldNot(HaveOccurred(), "do request") Expect(statusCode).To(Equal(http.StatusFound)) }) }) Context("test apisix/admin/oidc/callback", func() { It("should return status-code 200", func() { statusCode, err := accessOidcCallback(&OidcCookie) Expect(err).ShouldNot(HaveOccurred(), "do request") Expect(statusCode).To(Equal(http.StatusOK)) }) }) Context("access apisix/admin/routes with cookie", func() { It("should return status-code 200", func() { statusCode, err := accessRoutesWithCookie(true, OidcCookie) Expect(err).ShouldNot(HaveOccurred(), "do request") Expect(statusCode).To(Equal(http.StatusOK)) }) }) Context("access apisix/admin/oidc/logout with cookie", func() { It("should return status-code 200", func() { statusCode, err := accessOidcLogoutWithCookie(true, OidcCookie) Expect(err).ShouldNot(HaveOccurred(), "do request") Expect(statusCode).To(Equal(http.StatusOK)) }) }) Context("access apisix/admin/routes with invalid cookie", func() { It("should return status-code 401", func() { statusCode, err := accessRoutesWithCookie(false, OidcCookie) Expect(err).ShouldNot(HaveOccurred(), "do request") Expect(statusCode).To(Equal(http.StatusUnauthorized)) }) }) Context("access apisix/admin/oidc/logout with invalid cookie", func() { It("should return status-code 403", func() { statusCode, err := accessOidcLogoutWithCookie(false, OidcCookie) Expect(err).ShouldNot(HaveOccurred(), "do request") Expect(statusCode).To(Equal(http.StatusForbidden)) }) }) }) func accessOidcLogin() (int, error) { var err error var req *http.Request var resp *http.Response var Client = &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } createClientAndUser() req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/oidc/login", nil) resp, err = Client.Do(req) if err != nil { return 0, err } // return status-code return resp.StatusCode, err } func createClientAndUser() string { client := gocloak.NewClient("http://127.0.0.1:8080") ctx := context.Background() token, _ := client.LoginAdmin(ctx, "admin", "admin", "master") redirectURIs := []string{"http://127.0.0.1:9000/*"} _, _ = client.CreateClient(ctx, token.AccessToken, "master", gocloak.Client{ ClientID: gocloak.StringP("dashboard"), Secret: gocloak.StringP("dashboard"), RedirectURIs: &redirectURIs, }) username := GetRandomString(3) user := gocloak.User{ FirstName: gocloak.StringP(GetRandomString(3)), LastName: gocloak.StringP(GetRandomString(3)), Email: gocloak.StringP(GetRandomString(3)), Enabled: gocloak.BoolP(true), Username: gocloak.StringP(username), } id, _ := client.CreateUser(ctx, token.AccessToken, "master", user) _ = client.SetPassword(ctx, token.AccessToken, id, "master", "password", false) return username } func accessOidcCallback(OidcCookie *[]http.Cookie) (int, error) { var authenticationUrl string var loginUrl string var err error var req *http.Request var resp *http.Response var client = &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } username := createClientAndUser() // access apisix/admin/oidc/login to get the authentication-url req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/oidc/login", nil) resp, err = client.Do(req) if err != nil { return 0, err } authenticationUrl = resp.Header.Get("Location") // access the authentication-url req, _ = http.NewRequest("GET", authenticationUrl, nil) resp, err = client.Do(req) if err != nil { return 0, err } // get the login-url from html body, _ := ioutil.ReadAll(resp.Body) dom, _ := goquery.NewDocumentFromReader(strings.NewReader(string(body))) dom.Find("#kc-form-login").Each(func(i int, selection *goquery.Selection) { loginUrl = selection.Get(0).Attr[2].Val }) // set username & password formValues := url.Values{} formValues.Set("username", username) formValues.Set("password", "password") formDataStr := formValues.Encode() formDataBytes := []byte(formDataStr) formBytesReader := bytes.NewReader(formDataBytes) //fmt.Printf("loginUrl: %s/n", loginUrl) req, _ = http.NewRequest("POST", loginUrl, formBytesReader) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // set cookies cookies := resp.Cookies() for _, cookie := range cookies { req.AddCookie(cookie) } // access the login-url to login resp, err = client.Do(req) if err != nil { return 0, err } // access apisix/admin/oidc/login with code callbackUrl := resp.Header.Get("Location") req, _ = http.NewRequest("GET", callbackUrl, nil) resp, err = client.Do(req) if err != nil { return 0, err } // save cookie cookies = resp.Cookies() for _, cookie := range cookies { *OidcCookie = append(*OidcCookie, *cookie) } // return status-code return resp.StatusCode, err } func accessRoutesWithCookie(setCookie bool, OidcCookie []http.Cookie) (int, error) { var err error var req *http.Request var resp *http.Response var client http.Client req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/routes", nil) // set cookie or not if setCookie { for _, cookie := range OidcCookie { req.AddCookie(&cookie) } } // access apisix/admin/routes resp, err = client.Do(req) if err != nil { return 0, err } // return status-code return resp.StatusCode, err } func accessOidcLogoutWithCookie(setCookie bool, OidcCookie []http.Cookie) (int, error) { var err error var req *http.Request var resp *http.Response var client http.Client req, _ = http.NewRequest("GET", "http://127.0.0.1:9000/apisix/admin/oidc/logout", nil) // set cookie or not if setCookie { for _, cookie := range OidcCookie { req.AddCookie(&cookie) } } // access apisix/admin/oidc/logout resp, err = client.Do(req) if err != nil { return 0, err } // return status-code return resp.StatusCode, err } func GetRandomString(l int) string { str := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" bytes := []byte(str) var result []byte r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < l; i++ { result = append(result, bytes[r.Intn(len(bytes))]) } return string(result) }