Add API to ping OIDC endpoint

This commit adds an API to help admin verify the OIDC endpoint is a
valid one.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2019-07-17 16:19:56 +08:00
parent e0e6a1d30b
commit 96e2e0b145
7 changed files with 189 additions and 0 deletions

View File

@ -3478,6 +3478,39 @@ paths:
description: The robot account is not found.
'500':
description: Unexpected internal errors.
'/system/oidc/ping':
post:
summary: Test the OIDC endpoint.
description: Test the OIDC endpoint, the setting of the endpoint is provided in the request. This API can only
be called by system admin.
tags:
- Products
- System
parameters:
- name: endpoint
in: body
description: Request body for OIDC endpoint to be tested.
required: true
schema:
type: object
properties:
url:
type: string
description: The URL of OIDC endpoint to be tested.
verify_cert:
type: boolean
description: Whether the certificate should be verified
responses:
'200':
description: The specified robot account is successfully deleted.
'400':
description: The ping failed
'401':
description: User need to log in first.
'403':
description: User does not have permission to call this API
'500':
description: Unexpected internal errors.
'/system/CVEWhitelist':
get:
summary: Get the system level whitelist of CVE.

View File

@ -206,3 +206,19 @@ func RefreshToken(ctx context.Context, token *Token) (*Token, error) {
}
return &Token{Token: *t, IDToken: it}, nil
}
// Conn wraps connection info of an OIDC endpoint
type Conn struct {
URL string `json:"url"`
VerifyCert bool `json:"verify_cert"`
}
// TestEndpoint tests whether the endpoint is a valid OIDC endpoint.
// The nil return value indicates the success of the test
func TestEndpoint(conn Conn) error {
// gooidc will try to call the discovery api when creating the provider and that's all we need to check
ctx := clientCtx(context.Background(), conn.VerifyCert)
_, err := gooidc.NewProvider(ctx, conn.URL)
return err
}

View File

@ -97,3 +97,16 @@ func TestAuthCodeURL(t *testing.T) {
assert.Equal(t, "offline", q.Get("access_type"))
assert.False(t, strings.Contains(q.Get("scope"), "offline_access"))
}
func TestTestEndpoint(t *testing.T) {
c1 := Conn{
URL: googleEndpoint,
VerifyCert: true,
}
c2 := Conn{
URL: "https://www.baidu.com",
VerifyCert: false,
}
assert.Nil(t, TestEndpoint(c1))
assert.NotNil(t, TestEndpoint(c2))
}

View File

@ -145,6 +145,7 @@ func init() {
beego.Router("/api/system/gc/schedule", &GCAPI{}, "get:Get;put:Put;post:Post")
beego.Router("/api/system/scanAll/schedule", &ScanAllAPI{}, "get:Get;put:Put;post:Post")
beego.Router("/api/system/CVEWhitelist", &SysCVEWhitelistAPI{}, "get:Get;put:Put")
beego.Router("/api/system/oidc/ping", &OIDCAPI{}, "post:Ping")
beego.Router("/api/projects/:pid([0-9]+)/robots/", &RobotAPI{}, "post:Post;get:List")
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &RobotAPI{}, "get:Get;put:Put;delete:Delete")

56
src/core/api/oidc.go Normal file
View File

@ -0,0 +1,56 @@
// Copyright Project Harbor Authors
//
// Licensed 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 api
import (
"errors"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/common/utils/oidc"
)
// OIDCAPI handles the requests to /api/system/oidc/xxx
type OIDCAPI struct {
BaseController
}
// Prepare validates the request initially
func (oa *OIDCAPI) Prepare() {
oa.BaseController.Prepare()
if !oa.SecurityCtx.IsAuthenticated() {
oa.SendUnAuthorizedError(errors.New("unauthorized"))
return
}
if !oa.SecurityCtx.IsSysAdmin() {
msg := "only system admin has permission to access this API"
log.Errorf(msg)
oa.SendForbiddenError(errors.New(msg))
return
}
}
// Ping will handles the request to test connection to OIDC endpoint
func (oa *OIDCAPI) Ping() {
var c oidc.Conn
if err := oa.DecodeJSONReq(&c); err != nil {
log.Error("Failed to decode JSON request.")
oa.SendBadRequestError(err)
return
}
if err := oidc.TestEndpoint(c); err != nil {
log.Errorf("Failed to verify connection: %+v, err: %v", c, err)
oa.SendBadRequestError(err)
return
}
}

69
src/core/api/oidc_test.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright Project Harbor Authors
//
// Licensed 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 api
import (
"github.com/goharbor/harbor/src/common/utils/oidc"
"net/http"
"testing"
)
func TestOIDCAPI_Ping(t *testing.T) {
url := "/api/system/oidc/ping"
cases := []*codeCheckingCase{
{ // 401
request: &testingRequest{
method: http.MethodPost,
bodyJSON: oidc.Conn{},
url: url,
},
code: http.StatusUnauthorized,
},
{ // 403
request: &testingRequest{
method: http.MethodPost,
bodyJSON: oidc.Conn{},
url: url,
credential: nonSysAdmin,
},
code: http.StatusForbidden,
},
{ // 400
request: &testingRequest{
method: http.MethodPost,
bodyJSON: oidc.Conn{
URL: "https://www.baidu.com",
VerifyCert: true,
},
url: url,
credential: sysAdmin,
},
code: http.StatusBadRequest,
},
{ // 200
request: &testingRequest{
method: http.MethodPost,
bodyJSON: oidc.Conn{
URL: "https://accounts.google.com",
VerifyCert: true,
},
url: url,
credential: sysAdmin,
},
code: http.StatusOK,
},
}
runCodeCheckingCases(t, cases...)
}

View File

@ -97,6 +97,7 @@ func initRouters() {
beego.Router("/api/system/gc/schedule", &api.GCAPI{}, "get:Get;put:Put;post:Post")
beego.Router("/api/system/scanAll/schedule", &api.ScanAllAPI{}, "get:Get;put:Put;post:Post")
beego.Router("/api/system/CVEWhitelist", &api.SysCVEWhitelistAPI{}, "get:Get;put:Put")
beego.Router("/api/system/oidc/ping", &api.OIDCAPI{}, "post:Ping")
beego.Router("/api/logs", &api.LogAPI{})