Skip to content

Commit

Permalink
BED-4766: API Endpoint to List OIDC Providers (#896)
Browse files Browse the repository at this point in the history
* BED-4851 Updated CreateOIDCProvider endpoint and relevant database interactions to match new design

* BED-4851 updates to conform to new design

* BED-4851 cleanup import

* BED-4851 added integration test for TestBloodhoundDB_CreateSSOProviderWithTransaction

* BED-4851 removed name from oidc_providers

* BED-4851 updated tests with removed name field

* BED-4851 idempotent saml_providers constraint

* BED-4851 moved responsibility of slug creation to the db layer. Properly backfill saml_providers with the new sso_providers key. Added new enum type for sso_provider types

* generate

* removed test I was playing with and accidentally committed, oops. Added a mapping from AuthProvider to the new SSOProviderType enum

* fix returning error from CreateSSOPRovider

* added test for invalid SessionAuthProvider

* fixed test, better naming on sso provider types

* list without sortinga and filtering complete

* removed no longer used SetupTransaction

* unit and integration test passed

* adjust list endpoint after removing name in oidc provider

* cleanup for pr

* integrate new sso auth workflow to outdated test case

* refactoring response struct after merged change on ssoProvider

* db call optimization with sql joins

* addresses duplicate ssoprovider creation in test function

* Remove authmanageproviders permission from API route

Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>

* addresses nits

* Update cmd/api/src/api/v2/analysisrequest.go

Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>

* Update cmd/api/src/api/v2/auth/sso.go

Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>

* Update cmd/api/src/api/v2/auth/sso.go

Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>

* Update cmd/api/src/api/v2/auth/sso.go

Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>

---------

Co-authored-by: Michael Lipka <mvlipka@gmail.com>
Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent dac0bbb commit 403c45c
Show file tree
Hide file tree
Showing 17 changed files with 611 additions and 68 deletions.
1 change: 1 addition & 0 deletions cmd/api/src/api/registration/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func registerV2Auth(cfg config.Configuration, db database.Database, permissions

// SSO
routerInst.POST("/api/v2/sso-providers/oidc", managementResource.CreateOIDCProvider).CheckFeatureFlag(db, appcfg.FeatureOIDCSupport).RequirePermissions(permissions.AuthManageProviders),
routerInst.GET("/api/v2/sso-providers", managementResource.ListAuthProviders).CheckFeatureFlag(db, appcfg.FeatureOIDCSupport),

// Permissions
routerInst.GET("/api/v2/permissions", managementResource.ListPermissions).RequirePermissions(permissions.AuthManageSelf),
Expand Down
5 changes: 3 additions & 2 deletions cmd/api/src/api/tools/analysis_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ package tools

import (
"fmt"
"net/http"
"strings"

"github.com/specterops/bloodhound/src/api"
v2 "github.com/specterops/bloodhound/src/api/v2"
"github.com/specterops/bloodhound/src/database/types"
"github.com/specterops/bloodhound/src/model/appcfg"
"github.com/teambition/rrule-go"
"net/http"
"strings"
)

type ScheduledAnalysisConfiguration struct {
Expand Down
7 changes: 4 additions & 3 deletions cmd/api/src/api/tools/analysis_schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/golang/mock/gomock"
"github.com/gorilla/mux"
"github.com/specterops/bloodhound/headers"
"github.com/specterops/bloodhound/mediatypes"
"github.com/specterops/bloodhound/src/api/tools"
"github.com/specterops/bloodhound/src/ctx"
"github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"testing"
)

func TestToolContainer_GetScheduledAnalysisConfiguration_Errors(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/api/v2/analysisrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ package v2

import (
"database/sql"
"github.com/specterops/bloodhound/src/model/appcfg"
"net/http"

"github.com/specterops/bloodhound/src/model/appcfg"
"github.com/specterops/bloodhound/errors"
"github.com/specterops/bloodhound/log"
"github.com/specterops/bloodhound/src/api"
Expand Down
124 changes: 124 additions & 0 deletions cmd/api/src/api/v2/auth/sso.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2024 Specter Ops, Inc.
//
// Licensed under the Apache License, Version 2.0
// 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.
//
// SPDX-License-Identifier: Apache-2.0

package auth

import (
"net/http"
"strings"

"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/model"
"gorm.io/gorm/utils"
)

// AuthProvider represents a unified SSO provider (either OIDC or SAML)
type AuthProvider struct {
ID int32 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Slug string `json:"slug"`
Details interface{} `json:"details"`
}

// ListAuthProviders lists all available SSO providers (SAML and OIDC) with sorting and filtering
func (s ManagementResource) ListAuthProviders(response http.ResponseWriter, request *http.Request) {
var (
ctx = request.Context()
queryParams = request.URL.Query()
sortByColumns = queryParams[api.QueryParameterSortBy]
order []string
queryFilters model.QueryParameterFilterMap
sqlFilter model.SQLFilter
ssoProviders []model.SSOProvider
providers []AuthProvider
err error
queryFilterParser = model.NewQueryParameterFilterParser()
)

for _, column := range sortByColumns {
var descending bool
if strings.HasPrefix(column, "-") {
descending = true
column = column[1:]
}

if !model.SSOProviderSortableFields(column) {
api.WriteErrorResponse(ctx, api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsNotSortable, request), response)
return
}

if descending {
order = append(order, column+" desc")
} else {
order = append(order, column)
}
}

// Set default order by created_at if no sorting is specified
if len(order) == 0 {
order = append(order, "created_at")
}

if queryFilters, err = queryFilterParser.ParseQueryParameterFilters(request); err != nil {
api.WriteErrorResponse(ctx, api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsBadQueryParameterFilters, request), response)
} else {
for name, filters := range queryFilters {
if validPredicates, err := model.SSOProviderValidFilterPredicates(name); err != nil {
api.WriteErrorResponse(ctx, api.BuildErrorResponse(http.StatusBadRequest, err.Error(), request), response)
return
} else {
for i, filter := range filters {
if !utils.Contains(validPredicates, string(filter.Operator)) {
api.WriteErrorResponse(ctx, api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsFilterPredicateNotSupported, request), response)
return
}
queryFilters[name][i].IsStringData = model.SSOProviderIsStringField(filter.Name)
}
}
}

if sqlFilter, err = queryFilters.BuildSQLFilter(); err != nil {
api.WriteErrorResponse(ctx, api.BuildErrorResponse(http.StatusBadRequest, "error building SQL for filter", request), response)
} else if ssoProviders, err = s.db.GetAllSSOProviders(ctx, strings.Join(order, ", "), sqlFilter); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
for _, ssoProvider := range ssoProviders {
provider := AuthProvider{
ID: ssoProvider.ID,
Name: ssoProvider.Name,
Type: ssoProvider.Type.String(),
Slug: ssoProvider.Slug,
}

switch ssoProvider.Type {
case model.SessionAuthProviderOIDC:
if ssoProvider.OIDCProvider != nil {
provider.Details = ssoProvider.OIDCProvider
}
case model.SessionAuthProviderSAML:
if ssoProvider.SAMLProvider != nil {
provider.Details = ssoProvider.SAMLProvider
}
}

providers = append(providers, provider)
}

api.WriteBasicResponse(ctx, providers, http.StatusOK, response)
}
}
}
Loading

0 comments on commit 403c45c

Please sign in to comment.