200f8d477127ef6ce6f67ef60f738d818f27677f
[nonrtric/plt/sme.git] / capifcore / internal / keycloak / keycloak.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2023: Nordix Foundation
6 //   %%
7 //   Licensed under the Apache License, Version 2.0 (the "License");
8 //   you may not use this file except in compliance with the License.
9 //   You may obtain a copy of the License at
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
13 //   Unless required by applicable law or agreed to in writing, software
14 //   distributed under the License is distributed on an "AS IS" BASIS,
15 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 //   See the License for the specific language governing permissions and
17 //   limitations under the License.
18 //   ========================LICENSE_END===================================
19 //
20
21 package keycloak
22
23 import (
24         "encoding/json"
25         "errors"
26         "io"
27         "net/http"
28         "net/url"
29
30         log "github.com/sirupsen/logrus"
31         "oransc.org/nonrtric/capifcore/internal/config"
32         "oransc.org/nonrtric/capifcore/internal/restclient"
33 )
34
35 //go:generate mockery --name AccessManagement
36 type AccessManagement interface {
37         // Get JWT token for a client.
38         // Returns JWT token if client exits and credentials are correct otherwise returns error.
39         GetToken(realm string, data map[string][]string) (Jwttoken, error)
40         // Add new client in keycloak
41         AddClient(clientId string, realm string) error
42         // Returns information about client including secret
43         GetClientRepresentation(clientId string, realm string) (*Client, error)
44 }
45
46 type AdminUser struct {
47         User     string
48         Password string
49 }
50
51 type KeycloakManager struct {
52         keycloakServerUrl string
53         admin             AdminUser
54         realms            map[string]string
55         client            restclient.HTTPClient
56 }
57
58 func NewKeycloakManager(cfg *config.Config, c restclient.HTTPClient) *KeycloakManager {
59
60         keycloakUrl := "http://" + cfg.AuthorizationServer.Host + ":" + cfg.AuthorizationServer.Port
61
62         return &KeycloakManager{
63                 keycloakServerUrl: keycloakUrl,
64                 client:            c,
65                 admin: AdminUser{
66                         User:     cfg.AuthorizationServer.AdminUser.User,
67                         Password: cfg.AuthorizationServer.AdminUser.Password,
68                 },
69                 realms: cfg.AuthorizationServer.Realms,
70         }
71 }
72
73 type Jwttoken struct {
74         AccessToken      string `json:"access_token"`
75         IDToken          string `json:"id_token"`
76         ExpiresIn        int    `json:"expires_in"`
77         RefreshExpiresIn int    `json:"refresh_expires_in"`
78         RefreshToken     string `json:"refresh_token"`
79         TokenType        string `json:"token_type"`
80         NotBeforePolicy  int    `json:"not-before-policy"`
81         SessionState     string `json:"session_state"`
82         Scope            string `json:"scope"`
83 }
84
85 func (km *KeycloakManager) GetToken(realm string, data map[string][]string) (Jwttoken, error) {
86         var jwt Jwttoken
87         getTokenUrl := km.keycloakServerUrl + "/realms/" + realm + "/protocol/openid-connect/token"
88         resp, err := http.PostForm(getTokenUrl, data)
89
90         if err != nil {
91                 return jwt, err
92         }
93
94         defer resp.Body.Close()
95         body, err := io.ReadAll(resp.Body)
96
97         if err != nil {
98                 return jwt, err
99         }
100
101         if resp.StatusCode != http.StatusOK {
102                 return jwt, errors.New(string(body))
103         }
104
105         json.Unmarshal([]byte(body), &jwt)
106         return jwt, nil
107 }
108
109 type Client struct {
110         AdminURL                     string  `json:"adminUrl,omitempty"`
111         AuthorizationServicesEnabled *bool   `json:"authorizationServicesEnabled,omitempty"`
112         BearerOnly                   bool    `json:"bearerOnly,omitempty"`
113         ClientID                     string  `json:"clientId,omitempty"`
114         Enabled                      bool    `json:"enabled,omitempty"`
115         ID                           *string `json:"id,omitempty"`
116         PublicClient                 bool    `json:"publicClient,omitempty"`
117         RootURL                      string  `json:"rootUrl,omitempty"`
118         Secret                       *string `json:"secret,omitempty"`
119         ServiceAccountsEnabled       bool    `json:"serviceAccountsEnabled,omitempty"`
120 }
121
122 func (km *KeycloakManager) AddClient(clientId string, realm string) error {
123
124         data := url.Values{"grant_type": {"password"}, "username": {km.admin.User}, "password": {km.admin.Password}, "client_id": {"admin-cli"}}
125         token, err := km.GetToken("master", data)
126         if err != nil {
127                 log.Errorf("error wrong credentials or url %v\n", err)
128                 return err
129         }
130
131         createClientUrl := km.keycloakServerUrl + "/admin/realms/" + realm + "/clients"
132         newClient := map[string]interface{}{"clientId": clientId, "serviceAccountsEnabled": true}
133
134         body, err := json.Marshal(newClient)
135         if err != nil {
136                 return err
137         }
138
139         var headers = map[string]string{"Content-Type": "application/json", "Authorization": "Bearer " + token.AccessToken}
140         if err := restclient.Post(createClientUrl, body, headers, km.client); err != nil {
141                 log.Errorf("addClient - error with http request: %+v\n", err)
142                 return err
143         }
144
145         log.Debug("Created new client")
146         return nil
147
148 }
149
150 func (km *KeycloakManager) GetClientRepresentation(clientId string, realm string) (*Client, error) {
151
152         data := url.Values{"grant_type": {"password"}, "username": {km.admin.User}, "password": {km.admin.Password}, "client_id": {"admin-cli"}}
153         token, err := km.GetToken("master", data)
154         if err != nil {
155                 log.Errorf("error wrong credentials or url %v\n", err)
156                 return nil, err
157         }
158
159         createClientUrl, _ := url.Parse(km.keycloakServerUrl + "/admin/realms/" + realm + "/clients")
160         q := createClientUrl.Query()
161         q.Add("clientId", clientId)
162         createClientUrl.RawQuery = q.Encode()
163
164         var headers = map[string]string{"Content-Type": "application/json", "Authorization": "Bearer " + token.AccessToken}
165
166         if resp, err := restclient.Get(createClientUrl.String(), headers, km.client); err == nil {
167                 var client []Client
168
169                 if err = json.Unmarshal(resp, &client); err != nil {
170                         log.Errorf("error unmarshal keycloak client object: %+v\n", err)
171                         return nil, err
172                 }
173
174                 if len(client) > 0 {
175                         return &client[0], nil
176                 }
177                 return nil, nil
178
179         } else {
180                 log.Errorf("error with http request: %+v\n", err)
181                 return nil, err
182         }
183
184 }