--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// 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.
+// ========================LICENSE_END===================================
+//
+
+package securityapi
+
+import (
+ "github.com/labstack/echo/v4"
+)
+
+func (tokenReq *AccessTokenReq) GetAccessTokenReq(ctx echo.Context) {
+ clientId := ctx.FormValue("client_id")
+ clientSecret := ctx.FormValue("client_secret")
+ scope := ctx.FormValue("scope")
+ grantType := ctx.FormValue("grant_type")
+
+ tokenReq.ClientId = clientId
+ tokenReq.ClientSecret = &clientSecret
+ tokenReq.Scope = &scope
+ tokenReq.GrantType = AccessTokenReqGrantType(grantType)
+
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// 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.
+// ========================LICENSE_END===================================
+//
+
+package securityapi
+
+import (
+ "strings"
+)
+
+func (tokenReq AccessTokenReq) Validate() (bool, AccessTokenErr) {
+
+ if tokenReq.ClientId == "" {
+ return false, createAccessTokenError(AccessTokenErrErrorInvalidRequest, "Invalid request")
+ }
+
+ if tokenReq.GrantType != AccessTokenReqGrantTypeClientCredentials {
+ return false, createAccessTokenError(AccessTokenErrErrorInvalidGrant, "Invalid value for grant_type")
+ }
+
+ //3gpp#aefId1:apiName1,apiName2,…apiNameX;aefId2:apiName1,apiName2,…apiNameY;…aefIdN:apiName1,apiName2,…apiNameZ
+ if tokenReq.Scope != nil {
+ scope := strings.Split(*tokenReq.Scope, "#")
+ if len(scope) < 2 {
+ return false, createAccessTokenError(AccessTokenErrErrorInvalidScope, "Malformed scope")
+ }
+ if scope[0] != "3gpp" {
+ return false, createAccessTokenError(AccessTokenErrErrorInvalidScope, "Scope should start with 3gpp")
+ }
+ aefList := strings.Split(scope[1], ";")
+ for _, aef := range aefList {
+ apiList := strings.Split(aef, ":")
+ if len(apiList) < 2 {
+ return false, createAccessTokenError(AccessTokenErrErrorInvalidScope, "Malformed scope")
+ }
+ }
+ }
+ return true, AccessTokenErr{}
+}
+
+func createAccessTokenError(err AccessTokenErrError, message string) AccessTokenErr {
+ return AccessTokenErr{
+ Error: err,
+ ErrorDescription: &message,
+ }
+}
--- /dev/null
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// 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.
+// ========================LICENSE_END===================================
+//
+
+package securityapi
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestValidateClientIdNotPresent(t *testing.T) {
+ accessTokenUnderTest := AccessTokenReq{}
+ valid, err := accessTokenUnderTest.Validate()
+
+ assert.Equal(t, false, valid)
+ assert.Equal(t, AccessTokenErrErrorInvalidRequest, err.Error)
+ assert.Equal(t, "Invalid request", *err.ErrorDescription)
+}
+
+func TestValidateGrantType(t *testing.T) {
+ accessTokenUnderTest := AccessTokenReq{
+ ClientId: "clientId",
+ GrantType: AccessTokenReqGrantType(""),
+ }
+ valid, err := accessTokenUnderTest.Validate()
+
+ assert.Equal(t, false, valid)
+ assert.Equal(t, AccessTokenErrErrorInvalidGrant, err.Error)
+ assert.Equal(t, "Invalid value for grant_type", *err.ErrorDescription)
+
+ accessTokenUnderTest.GrantType = AccessTokenReqGrantType("client_credentials")
+ valid, err = accessTokenUnderTest.Validate()
+ assert.Equal(t, true, valid)
+}
+
+func TestValidateScopeNotValid(t *testing.T) {
+ scope := "scope#aefId:path"
+ accessTokenUnderTest := AccessTokenReq{
+ ClientId: "clientId",
+ GrantType: ("client_credentials"),
+ Scope: &scope,
+ }
+ valid, err := accessTokenUnderTest.Validate()
+
+ assert.Equal(t, false, valid)
+ assert.Equal(t, AccessTokenErrErrorInvalidScope, err.Error)
+ assert.Equal(t, "Scope should start with 3gpp", *err.ErrorDescription)
+
+ scope = "3gpp#aefId:path"
+ accessTokenUnderTest.Scope = &scope
+ valid, err = accessTokenUnderTest.Validate()
+ assert.Equal(t, true, valid)
+}
+
+func TestValidateScopeMalformed(t *testing.T) {
+ scope := "3gpp"
+ accessTokenUnderTest := AccessTokenReq{
+ ClientId: "clientId",
+ GrantType: ("client_credentials"),
+ Scope: &scope,
+ }
+ valid, err := accessTokenUnderTest.Validate()
+
+ assert.Equal(t, false, valid)
+ assert.Equal(t, AccessTokenErrErrorInvalidScope, err.Error)
+ assert.Equal(t, "Malformed scope", *err.ErrorDescription)
+
+ scope = "3gpp#aefId"
+ accessTokenUnderTest.Scope = &scope
+ valid, err = accessTokenUnderTest.Validate()
+ assert.Equal(t, false, valid)
+ assert.Equal(t, AccessTokenErrErrorInvalidScope, err.Error)
+ assert.Equal(t, "Malformed scope", *err.ErrorDescription)
+
+ scope = "3gpp#aefId:path"
+ accessTokenUnderTest.Scope = &scope
+ valid, err = accessTokenUnderTest.Validate()
+ assert.Equal(t, true, valid)
+}
import (
"net/http"
"strings"
+ "time"
+ "github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"oransc.org/nonrtric/capifcore/internal/common29122"
"oransc.org/nonrtric/capifcore/internal/publishservice"
)
+var jwtKey = "my-secret-key"
+
type Security struct {
serviceRegister providermanagement.ServiceRegister
publishRegister publishservice.PublishRegister
}
func (s *Security) PostSecuritiesSecurityIdToken(ctx echo.Context, securityId string) error {
- clientId := ctx.FormValue("client_id")
- clientSecret := ctx.FormValue("client_secret")
- scope := ctx.FormValue("scope")
+ var accessTokenReq securityapi.AccessTokenReq
+ accessTokenReq.GetAccessTokenReq(ctx)
- if !s.invokerRegister.IsInvokerRegistered(clientId) {
- return sendCoreError(ctx, http.StatusBadRequest, "Invoker not registered")
+ if valid, err := accessTokenReq.Validate(); !valid {
+ return ctx.JSON(http.StatusBadRequest, err)
}
- if !s.invokerRegister.VerifyInvokerSecret(clientId, clientSecret) {
- return sendCoreError(ctx, http.StatusBadRequest, "Invoker secret not valid")
+
+ if !s.invokerRegister.IsInvokerRegistered(accessTokenReq.ClientId) {
+ return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorInvalidClient, "Invoker not registered")
}
- if scope != "" {
- scopeData := strings.Split(strings.Split(scope, "#")[1], ":")
- if !s.serviceRegister.IsFunctionRegistered(scopeData[0]) {
- return sendCoreError(ctx, http.StatusBadRequest, "Function not registered")
- }
- if !s.publishRegister.IsAPIPublished(scopeData[0], scopeData[1]) {
- return sendCoreError(ctx, http.StatusBadRequest, "API not published")
+
+ if !s.invokerRegister.VerifyInvokerSecret(accessTokenReq.ClientId, *accessTokenReq.ClientSecret) {
+ return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorUnauthorizedClient, "Invoker secret not valid")
+ }
+
+ if accessTokenReq.Scope != nil {
+ scope := strings.Split(*accessTokenReq.Scope, "#")
+ aefList := strings.Split(scope[1], ";")
+ for _, aef := range aefList {
+ apiList := strings.Split(aef, ":")
+ if !s.serviceRegister.IsFunctionRegistered(apiList[0]) {
+ return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorInvalidScope, "AEF Function not registered")
+ }
+ for _, api := range strings.Split(apiList[1], ",") {
+ if !s.publishRegister.IsAPIPublished(apiList[0], api) {
+ return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorInvalidScope, "API not published")
+ }
+ }
}
}
+ expirationTime := time.Now().Add(time.Hour).Unix()
+
+ claims := &jwt.MapClaims{
+ "iss": accessTokenReq.ClientId,
+ "exp": expirationTime,
+ "data": map[string]interface{}{
+ "scope": accessTokenReq.Scope,
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString([]byte(jwtKey))
+ if err != nil {
+ // If there is an error in creating the JWT return an internal server error
+ return err
+ }
+
accessTokenResp := securityapi.AccessTokenRsp{
- AccessToken: "asdadfsrt dsr t5",
- ExpiresIn: 0,
- Scope: &scope,
+ AccessToken: tokenString,
+ ExpiresIn: common29122.DurationSec(expirationTime),
+ Scope: accessTokenReq.Scope,
TokenType: "Bearer",
}
- err := ctx.JSON(http.StatusCreated, accessTokenResp)
+ err = ctx.JSON(http.StatusCreated, accessTokenResp)
if err != nil {
// Something really bad happened, tell Echo that our handler failed
return err
return ctx.NoContent(http.StatusNotImplemented)
}
-func sendCoreError(ctx echo.Context, code int, message string) error {
- pd := common29122.ProblemDetails{
- Cause: &message,
- Status: &code,
+func sendAccessTokenError(ctx echo.Context, code int, err securityapi.AccessTokenErrError, message string) error {
+ accessTokenErr := securityapi.AccessTokenErr{
+ Error: err,
+ ErrorDescription: &message,
}
- err := ctx.JSON(code, pd)
- return err
+ return ctx.JSON(code, accessTokenErr)
}
"github.com/labstack/echo/v4"
- "oransc.org/nonrtric/capifcore/internal/common29122"
-
invokermocks "oransc.org/nonrtric/capifcore/internal/invokermanagement/mocks"
servicemocks "oransc.org/nonrtric/capifcore/internal/providermanagement/mocks"
publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks"
aefId := "aefId"
path := "path"
data.Set("client_id", clientId)
- data.Add("client_secret", clientSecret)
- data.Add("grant_type", "client_credentials")
- data.Add("scope", "scope#"+aefId+":"+path)
+ data.Set("client_secret", clientSecret)
+ data.Set("grant_type", "client_credentials")
+ data.Set("scope", "3gpp#"+aefId+":"+path)
+
encodedData := data.Encode()
result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
err := result.UnmarshalBodyToObject(&resultResponse)
assert.NoError(t, err, "error unmarshaling response")
assert.NotEmpty(t, resultResponse.AccessToken)
- assert.Equal(t, "scope#"+aefId+":"+path, *resultResponse.Scope)
+ assert.Equal(t, "3gpp#"+aefId+":"+path, *resultResponse.Scope)
assert.Equal(t, securityapi.AccessTokenRspTokenTypeBearer, resultResponse.TokenType)
- assert.Equal(t, common29122.DurationSec(0), resultResponse.ExpiresIn)
invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", clientId)
invokerRegisterMock.AssertCalled(t, "VerifyInvokerSecret", clientId, clientSecret)
serviceRegisterMock.AssertCalled(t, "IsFunctionRegistered", aefId)
data.Set("client_id", "id")
data.Add("client_secret", "secret")
data.Add("grant_type", "client_credentials")
- data.Add("scope", "scope#aefId:path")
+ data.Add("scope", "3gpp#aefId:path")
encodedData := data.Encode()
result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
- var problemDetails common29122.ProblemDetails
- err := result.UnmarshalBodyToObject(&problemDetails)
+ var errDetails securityapi.AccessTokenErr
+ err := result.UnmarshalBodyToObject(&errDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, securityapi.AccessTokenErrErrorInvalidClient, errDetails.Error)
errMsg := "Invoker not registered"
- assert.Equal(t, &errMsg, problemDetails.Cause)
+ assert.Equal(t, &errMsg, errDetails.ErrorDescription)
}
func TestPostSecurityIdTokenInvokerSecretNotValid(t *testing.T) {
data.Set("client_id", "id")
data.Add("client_secret", "secret")
data.Add("grant_type", "client_credentials")
- data.Add("scope", "scope#aefId:path")
+ data.Add("scope", "3gpp#aefId:path")
encodedData := data.Encode()
result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
- var problemDetails common29122.ProblemDetails
- err := result.UnmarshalBodyToObject(&problemDetails)
+ var errDetails securityapi.AccessTokenErr
+ err := result.UnmarshalBodyToObject(&errDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, securityapi.AccessTokenErrErrorUnauthorizedClient, errDetails.Error)
errMsg := "Invoker secret not valid"
- assert.Equal(t, &errMsg, problemDetails.Cause)
+ assert.Equal(t, &errMsg, errDetails.ErrorDescription)
}
func TestPostSecurityIdTokenFunctionNotRegistered(t *testing.T) {
data.Set("client_id", "id")
data.Add("client_secret", "secret")
data.Add("grant_type", "client_credentials")
- data.Add("scope", "scope#aefId:path")
+ data.Add("scope", "3gpp#aefId:path")
encodedData := data.Encode()
result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
- var problemDetails common29122.ProblemDetails
- err := result.UnmarshalBodyToObject(&problemDetails)
+ var errDetails securityapi.AccessTokenErr
+ err := result.UnmarshalBodyToObject(&errDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
- errMsg := "Function not registered"
- assert.Equal(t, &errMsg, problemDetails.Cause)
+ assert.Equal(t, securityapi.AccessTokenErrErrorInvalidScope, errDetails.Error)
+ errMsg := "AEF Function not registered"
+ assert.Equal(t, &errMsg, errDetails.ErrorDescription)
}
func TestPostSecurityIdTokenAPINotPublished(t *testing.T) {
data.Set("client_id", "id")
data.Add("client_secret", "secret")
data.Add("grant_type", "client_credentials")
- data.Add("scope", "scope#aefId:path")
+ data.Add("scope", "3gpp#aefId:path")
encodedData := data.Encode()
result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
assert.Equal(t, http.StatusBadRequest, result.Code())
- var problemDetails common29122.ProblemDetails
- err := result.UnmarshalBodyToObject(&problemDetails)
+ var errDetails securityapi.AccessTokenErr
+ err := result.UnmarshalBodyToObject(&errDetails)
assert.NoError(t, err, "error unmarshaling response")
- badRequest := http.StatusBadRequest
- assert.Equal(t, &badRequest, problemDetails.Status)
+ assert.Equal(t, securityapi.AccessTokenErrErrorInvalidScope, errDetails.Error)
errMsg := "API not published"
- assert.Equal(t, &errMsg, problemDetails.Cause)
+ assert.Equal(t, &errMsg, errDetails.ErrorDescription)
}
func getEcho(serviceRegister providermanagement.ServiceRegister, publishRegister publishservice.PublishRegister, invokerRegister invokermanagement.InvokerRegister) *echo.Echo {