Switch API to ping OIDC endpoint to new model

This commit updates the API POST /api/v2.0/system/oidc/ping to new
programming model, in which the code will be generated by go-swagger.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2021-03-04 11:47:09 +08:00
parent d36994b8b0
commit e96c1cbced
19 changed files with 83 additions and 168 deletions

View File

@ -1737,37 +1737,6 @@ paths:
$ref: '#/definitions/NotFoundChartAPIError' $ref: '#/definitions/NotFoundChartAPIError'
'500': '500':
$ref: '#/definitions/InternalChartAPIError' $ref: '#/definitions/InternalChartAPIError'
'/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: Ping succeeded. The OIDC endpoint is valid.
'400':
description: The ping failed
'401':
description: User need to log in first.
'403':
description: User does not have permission to call this API
'/system/CVEAllowlist': '/system/CVEAllowlist':
get: get:
summary: Get the system level allowlist of CVE. summary: Get the system level allowlist of CVE.

View File

@ -2215,6 +2215,37 @@ paths:
description: Not found the default root certificate. description: Not found the default root certificate.
'500': '500':
$ref: '#/responses/500' $ref: '#/responses/500'
/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:
- oidc
operationId: pingOIDC
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':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
/system/gc: /system/gc:
get: get:
summary: Get gc results. summary: Get gc results.

View File

@ -72,4 +72,5 @@ const (
ResourceReplicationPolicy = Resource("replication-policy") ResourceReplicationPolicy = Resource("replication-policy")
ResourceScanAll = Resource("scan-all") ResourceScanAll = Resource("scan-all")
ResourceSystemVolumes = Resource("system-volumes") ResourceSystemVolumes = Resource("system-volumes")
ResourceOIDCEndpoint = Resource("oidc-endpoint")
) )

View File

@ -60,5 +60,8 @@ var (
{Resource: rbac.ResourceScanAll, Action: rbac.ActionList}, {Resource: rbac.ResourceScanAll, Action: rbac.ActionList},
{Resource: rbac.ResourceSystemVolumes, Action: rbac.ActionRead}, {Resource: rbac.ResourceSystemVolumes, Action: rbac.ActionRead},
{Resource: rbac.ResourceOIDCEndpoint, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceOIDCEndpoint, Action: rbac.ActionRead},
} }
) )

View File

@ -122,7 +122,6 @@ func init() {
beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List") beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List")
beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete") beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/system/CVEAllowlist", &SysCVEAllowlistAPI{}, "get:Get;put:Put") beego.Router("/api/system/CVEAllowlist", &SysCVEAllowlistAPI{}, "get:Get;put:Put")
beego.Router("/api/system/oidc/ping", &OIDCAPI{}, "post:Ping")
beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List") beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List")

View File

@ -1,57 +0,0 @@
// 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/oidc"
"github.com/goharbor/harbor/src/lib/log"
)
// 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(errors.New("failed to verify connection"))
return
}
}

View File

@ -1,69 +0,0 @@
// 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

@ -24,11 +24,11 @@ import (
"github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/oidc"
"github.com/goharbor/harbor/src/core/api" "github.com/goharbor/harbor/src/core/api"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/oidc"
) )
const tokenKey = "oidc_token" const tokenKey = "oidc_token"

View File

@ -3,8 +3,9 @@ package oidc
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestSecretVerifyError(t *testing.T) { func TestSecretVerifyError(t *testing.T) {

View File

@ -22,10 +22,10 @@ import (
"github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/local" "github.com/goharbor/harbor/src/common/security/local"
"github.com/goharbor/harbor/src/common/utils/oidc"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/oidc"
) )
type idToken struct{} type idToken struct{}

View File

@ -24,9 +24,9 @@ import (
"github.com/goharbor/harbor/src/common/api" "github.com/goharbor/harbor/src/common/api"
"github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/local" "github.com/goharbor/harbor/src/common/security/local"
"github.com/goharbor/harbor/src/common/utils/oidc"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/oidc"
) )
var ( var (

View File

@ -16,13 +16,14 @@ package security
import ( import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/utils/oidc"
"github.com/goharbor/harbor/src/lib"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http" "net/http"
"testing" "testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestOIDCCli(t *testing.T) { func TestOIDCCli(t *testing.T) {

View File

@ -47,6 +47,7 @@ func New() http.Handler {
GCAPI: newGCAPI(), GCAPI: newGCAPI(),
QuotaAPI: newQuotaAPI(), QuotaAPI: newQuotaAPI(),
RetentionAPI: newRetentionAPI(), RetentionAPI: newRetentionAPI(),
OidcAPI: newOIDCAPI(),
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -0,0 +1,36 @@
package handler
import (
"context"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
oidcpkg "github.com/goharbor/harbor/src/pkg/oidc"
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/oidc"
)
type oidcAPI struct {
BaseAPI
}
func newOIDCAPI() *oidcAPI {
return &oidcAPI{}
}
func (o oidcAPI) PingOIDC(ctx context.Context, params oidc.PingOIDCParams) middleware.Responder {
if err := o.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceOIDCEndpoint); err != nil {
return o.SendError(ctx, err)
}
err := oidcpkg.TestEndpoint(oidcpkg.Conn{
URL: params.Endpoint.URL,
VerifyCert: params.Endpoint.VerifyCert,
})
if err != nil {
log.Errorf("Failed to verify connection: %+v, err: %v", params.Endpoint, err)
return o.SendError(ctx, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("failed to verify connection"))
}
return oidc.NewPingOIDCOK()
}

View File

@ -43,7 +43,6 @@ func registerLegacyRoutes() {
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post") beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
beego.Router("/api/"+version+"/system/CVEAllowlist", &api.SysCVEAllowlistAPI{}, "get:Get;put:Put") beego.Router("/api/"+version+"/system/CVEAllowlist", &api.SysCVEAllowlistAPI{}, "get:Get;put:Put")
beego.Router("/api/"+version+"/system/oidc/ping", &api.OIDCAPI{}, "post:Ping")
beego.Router("/api/"+version+"/replication/adapters", &api.ReplicationAdapterAPI{}, "get:List") beego.Router("/api/"+version+"/replication/adapters", &api.ReplicationAdapterAPI{}, "get:List")
beego.Router("/api/"+version+"/replication/adapterinfos", &api.ReplicationAdapterAPI{}, "get:ListAdapterInfos") beego.Router("/api/"+version+"/replication/adapterinfos", &api.ReplicationAdapterAPI{}, "get:ListAdapterInfos")