authorizationServer:
host: "localhost"
port: "8080"
+ admin:
+ user: "admin"
+ password: "secret"
realms:
invokerrealm: "invokerrealm"
"gopkg.in/yaml.v2"
)
+type AdminUser struct {
+ User string `yaml:"user"`
+ Password string `yaml:"password"`
+}
+
type AuthorizationServer struct {
- Port string `yaml:"port"`
- Host string `yaml:"host"`
- Realms map[string]string `yaml:"realms"`
+ Port string `yaml:"port"`
+ Host string `yaml:"host"`
+ AdminUser AdminUser `yaml:"admin"`
+ Realms map[string]string `yaml:"realms"`
}
type Config struct {
"net/http"
"net/url"
+ log "github.com/sirupsen/logrus"
"oransc.org/nonrtric/capifcore/internal/config"
+ "oransc.org/nonrtric/capifcore/internal/restclient"
)
//go:generate mockery --name AccessManagement
type AccessManagement interface {
// Get JWT token for a client.
// Returns JWT token if client exits and credentials are correct otherwise returns error.
- GetToken(clientId, clientPassword, scope string, realm string) (Jwttoken, error)
+ GetToken(realm string, data map[string][]string) (Jwttoken, error)
+ // Add new client in keycloak
+ AddClient(clientId string, realm string) error
+}
+
+type AdminUser struct {
+ User string
+ Password string
}
type KeycloakManager struct {
keycloakServerUrl string
+ admin AdminUser
realms map[string]string
+ client restclient.HTTPClient
}
-func NewKeycloakManager(cfg *config.Config) *KeycloakManager {
+func NewKeycloakManager(cfg *config.Config, c restclient.HTTPClient) *KeycloakManager {
keycloakUrl := "http://" + cfg.AuthorizationServer.Host + ":" + cfg.AuthorizationServer.Port
return &KeycloakManager{
keycloakServerUrl: keycloakUrl,
- realms: cfg.AuthorizationServer.Realms,
+ client: c,
+ admin: AdminUser{
+ User: cfg.AuthorizationServer.AdminUser.User,
+ Password: cfg.AuthorizationServer.AdminUser.Password,
+ },
+ realms: cfg.AuthorizationServer.Realms,
}
}
Scope string `json:"scope"`
}
-func (km *KeycloakManager) GetToken(clientId, clientPassword, scope string, realm string) (Jwttoken, error) {
+func (km *KeycloakManager) GetToken(realm string, data map[string][]string) (Jwttoken, error) {
var jwt Jwttoken
getTokenUrl := km.keycloakServerUrl + "/realms/" + realm + "/protocol/openid-connect/token"
- resp, err := http.PostForm(getTokenUrl,
- url.Values{"grant_type": {"client_credentials"}, "client_id": {clientId}, "client_secret": {clientPassword}})
+ resp, err := http.PostForm(getTokenUrl, data)
if err != nil {
return jwt, err
json.Unmarshal([]byte(body), &jwt)
return jwt, nil
}
+
+type Client struct {
+ AdminURL string `json:"adminUrl,omitempty"`
+ BearerOnly bool `json:"bearerOnly,omitempty"`
+ ClientID string `json:"clientId,omitempty"`
+ Enabled bool `json:"enabled,omitempty"`
+ PublicClient bool `json:"publicClient,omitempty"`
+ RootURL string `json:"rootUrl,omitempty"`
+ ServiceAccountsEnabled bool `json:"serviceAccountsEnabled,omitempty"`
+}
+
+func (km *KeycloakManager) AddClient(clientId string, realm string) error {
+ data := url.Values{"grant_type": {"password"}, "username": {km.admin.User}, "password": {km.admin.Password}, "client_id": {"admin-cli"}}
+ token, err := km.GetToken("master", data)
+ if err != nil {
+ log.Errorf("error wrong credentials or url %v\n", err)
+ return err
+ }
+
+ createClientUrl := km.keycloakServerUrl + "/admin/realms/" + realm + "/clients"
+ newClient := Client{
+ ClientID: clientId,
+ Enabled: true,
+ ServiceAccountsEnabled: true,
+ BearerOnly: false,
+ PublicClient: false,
+ }
+
+ body, _ := json.Marshal(newClient)
+ var headers = map[string]string{"Content-Type": "application/json", "Authorization": "Bearer " + token.AccessToken}
+ if error := restclient.Post(createClientUrl, body, headers, km.client); error != nil {
+ log.Errorf("error with http request: %+v\n", err)
+ return err
+ }
+
+ log.Info("Created new client")
+ return nil
+
+}
mock.Mock
}
-// GetToken provides a mock function with given fields: clientId, clientPassword, scope, realm
-func (_m *AccessManagement) GetToken(clientId string, clientPassword string, scope string, realm string) (keycloak.Jwttoken, error) {
- ret := _m.Called(clientId, clientPassword, scope, realm)
+// AddClient provides a mock function with given fields: clientId, realm
+func (_m *AccessManagement) AddClient(clientId string, realm string) error {
+ ret := _m.Called(clientId, realm)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(clientId, realm)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// GetToken provides a mock function with given fields: realm, data
+func (_m *AccessManagement) GetToken(realm string, data map[string][]string) (keycloak.Jwttoken, error) {
+ ret := _m.Called(realm, data)
var r0 keycloak.Jwttoken
var r1 error
- if rf, ok := ret.Get(0).(func(string, string, string, string) (keycloak.Jwttoken, error)); ok {
- return rf(clientId, clientPassword, scope, realm)
+ if rf, ok := ret.Get(0).(func(string, map[string][]string) (keycloak.Jwttoken, error)); ok {
+ return rf(realm, data)
}
- if rf, ok := ret.Get(0).(func(string, string, string, string) keycloak.Jwttoken); ok {
- r0 = rf(clientId, clientPassword, scope, realm)
+ if rf, ok := ret.Get(0).(func(string, map[string][]string) keycloak.Jwttoken); ok {
+ r0 = rf(realm, data)
} else {
r0 = ret.Get(0).(keycloak.Jwttoken)
}
- if rf, ok := ret.Get(1).(func(string, string, string, string) error); ok {
- r1 = rf(clientId, clientPassword, scope, realm)
+ if rf, ok := ret.Get(1).(func(string, map[string][]string) error); ok {
+ r1 = rf(realm, data)
} else {
r1 = ret.Error(1)
}
}
func Put(url string, body []byte, client HTTPClient) error {
- return do(http.MethodPut, url, body, ContentTypeJSON, client)
+ var header = map[string]string{"Content-Type": ContentTypeJSON}
+ return do(http.MethodPut, url, body, header, client)
}
-func do(method string, url string, body []byte, contentType string, client HTTPClient) error {
+func Post(url string, body []byte, header map[string]string, client HTTPClient) error {
+ return do(http.MethodPost, url, body, header, client)
+}
+
+func do(method string, url string, body []byte, header map[string]string, client HTTPClient) error {
if req, reqErr := http.NewRequest(method, url, bytes.NewBuffer(body)); reqErr == nil {
- req.Header.Set("Content-Type", contentType)
+ if len(header) > 0 {
+ setHeader(req, header)
+ }
if response, respErr := client.Do(req); respErr == nil {
if isResponseSuccess(response.StatusCode) {
return nil
}
}
+func setHeader(req *http.Request, header map[string]string) {
+ for key, element := range header {
+ req.Header.Set(key, element)
+ }
+}
+
func isResponseSuccess(statusCode int) bool {
return statusCode >= http.StatusOK && statusCode <= 299
}
StatusCode: tt.args.mockReturnStatus,
Body: io.NopCloser(bytes.NewReader(tt.args.mockReturnBody)),
}, tt.args.mockReturnError)
- err := do("PUT", tt.args.url, nil, "", &clientMock)
+ err := do("PUT", tt.args.url, nil, map[string]string{}, &clientMock)
assertions.Equal(tt.wantErr, err, tt.name)
})
}
func (newContext *ServiceSecurity) PrepareNewSecurityContext(services []publishserviceapi.ServiceAPIDescription) error {
securityMethods = []publishserviceapi.SecurityMethod{}
for i, securityInfo := range newContext.SecurityInfo {
-
if securityInfo.InterfaceDetails != nil {
addSecurityMethodsFromInterfaceDetails(securityInfo.InterfaceDetails.SecurityMethods, &securityInfo.PrefSecurityMethods)
checkNil := securityInfo.ApiId != nil && securityInfo.AefId != nil
if checkNil {
service := getServiceByApiId(&services, securityInfo.ApiId)
- afpProfile := service.GetAefProfileById(securityInfo.AefId)
+ if service != nil {
+ afpProfile := service.GetAefProfileById(securityInfo.AefId)
+ addSecurityMethodsFromAefProfile(afpProfile)
+ }
- addSecurityMethodsFromAefProfile(afpProfile)
}
}
import (
"fmt"
"net/http"
+ "net/url"
"path"
"strings"
"sync"
}
}
}
- jwtToken, err := s.keycloak.GetToken(accessTokenReq.ClientId, *accessTokenReq.ClientSecret, *accessTokenReq.Scope, "invokerrealm")
+ data := url.Values{"grant_type": {"client_credentials"}, "client_id": {accessTokenReq.ClientId}, "client_secret": {*accessTokenReq.ClientSecret}}
+ jwtToken, err := s.keycloak.GetToken("invokerrealm", data)
if err != nil {
return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorUnauthorizedClient, err.Error())
}
return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
}
+ err = s.keycloak.AddClient(apiInvokerId, "invokerrealm")
+ if err != nil {
+ return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
+ }
+
uri := ctx.Request().Host + ctx.Request().URL.String()
ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
Scope: "3gpp#aefIdpath",
}
accessMgmMock := keycloackmocks.AccessManagement{}
- accessMgmMock.On("GetToken", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(jwt, nil)
+ accessMgmMock.On("GetToken", mock.AnythingOfType("string"), mock.AnythingOfType("map[string][]string")).Return(jwt, nil)
requestHandler, _ := getEcho(&serviceRegisterMock, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
invokerRegisterMock.AssertCalled(t, "VerifyInvokerSecret", clientId, clientSecret)
serviceRegisterMock.AssertCalled(t, "IsFunctionRegistered", aefId)
publishRegisterMock.AssertCalled(t, "IsAPIPublished", aefId, path)
- accessMgmMock.AssertCalled(t, "GetToken", clientId, clientSecret, "3gpp#"+aefId+":"+path, "invokerrealm")
+ accessMgmMock.AssertNumberOfCalls(t, "GetToken", 1)
}
func TestPostSecurityIdTokenInvokerNotRegistered(t *testing.T) {
jwt := keycloak.Jwttoken{}
accessMgmMock := keycloackmocks.AccessManagement{}
- accessMgmMock.On("GetToken", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(jwt, errors.New("invalid_credentials"))
+ accessMgmMock.On("GetToken", mock.AnythingOfType("string"), mock.AnythingOfType("map[string][]string")).Return(jwt, errors.New("invalid_credentials"))
requestHandler, _ := getEcho(&serviceRegisterMock, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
invokerRegisterMock.AssertCalled(t, "VerifyInvokerSecret", clientId, clientSecret)
serviceRegisterMock.AssertCalled(t, "IsFunctionRegistered", aefId)
publishRegisterMock.AssertCalled(t, "IsAPIPublished", aefId, path)
- accessMgmMock.AssertCalled(t, "GetToken", clientId, clientSecret, "3gpp#"+aefId+":"+path, "invokerrealm")
+ accessMgmMock.AssertNumberOfCalls(t, "GetToken", 1)
}
func TestPutTrustedInvokerSuccessfully(t *testing.T) {
publishRegisterMock := publishmocks.PublishRegister{}
publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
- requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil)
+ accessMgmMock := keycloackmocks.AccessManagement{}
+ accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil)
+
+ requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
invokerId := "invokerId"
serviceSecurityUnderTest := getServiceSecurity(aefId, apiId)
assert.Equal(t, *security.SelSecurityMethod, publishserviceapi.SecurityMethodPKI)
}
invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
+ accessMgmMock.AssertCalled(t, "AddClient", invokerId, "invokerrealm")
}
publishRegisterMock := publishmocks.PublishRegister{}
publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
- requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil)
+ accessMgmMock := keycloackmocks.AccessManagement{}
+ accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil)
+
+ requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
invokerId := "invokerId"
serviceSecurityUnderTest := getServiceSecurity(aefId, apiId)
assert.Equal(t, publishserviceapi.SecurityMethodPSK, *security.SelSecurityMethod)
}
invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
-
+ accessMgmMock.AssertCalled(t, "AddClient", invokerId, "invokerrealm")
}
func TestPutTrustedInvokerNotFoundSecurityMethod(t *testing.T) {
publishRegisterMock := publishmocks.PublishRegister{}
publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
- requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil)
+ accessMgmMock := keycloackmocks.AccessManagement{}
+ accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil)
+
+ requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
invokerId := "invokerId"
serviceSecurityUnderTest := getServiceSecurity("aefId", "apiId")
assert.NoError(t, err, "error unmarshaling response")
assert.Equal(t, newNotifURL, string(resultResponse.NotificationDestination))
- // Update with an service security missing required NotificationDestination, should get 400 with problem details
+ // Update with a service security missing required NotificationDestination, should get 400 with problem details
invalidServiceSecurity := securityapi.ServiceSecurity{
SecurityInfo: []securityapi.SecurityInformation{
{
if err != nil {
log.Fatalf("Error loading configuration file\n: %s", err)
}
- km := keycloak.NewKeycloakManager(cfg)
+ km := keycloak.NewKeycloakManager(cfg, &http.Client{})
var group *echo.Group
// Register ProviderManagement