demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 2 | ################################################################################ |
| 3 | # Copyright 2021 highstreet technologies GmbH |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the 'License'); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an 'AS IS' BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | # |
| 17 | |
| 18 | # importing the sys, json, requests library |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 19 | from sqlite3 import TimeFromTicks |
| 20 | from jproperties import Properties |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 21 | import os |
| 22 | import sys |
| 23 | import json |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 24 | import time |
demx8as6 | b5ec030 | 2023-07-06 14:09:11 +0000 | [diff] [blame] | 25 | import re |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 26 | import requests |
| 27 | import getpass |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 28 | import warnings |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 29 | from typing import List |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 30 | |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 31 | warnings.filterwarnings('ignore', message='Unverified HTTPS request') |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 32 | |
| 33 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 34 | # global configurations |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 35 | def get_env(name): |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 36 | configs = Properties() |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 37 | envFile = os.path.dirname(os.path.abspath(__file__)) + '/' + '../' + '.env' |
| 38 | |
| 39 | with open(envFile, "rb") as read_prop: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 40 | configs.load(read_prop) |
demx8as6 | b5ec030 | 2023-07-06 14:09:11 +0000 | [diff] [blame] | 41 | value = configs.get(name).data |
| 42 | |
| 43 | regex = r"\$\{([^\}]+)\}" |
| 44 | matches = re.finditer(regex, value) |
| 45 | while True: |
| 46 | match = next(matches, None) |
| 47 | if match is None: |
| 48 | break |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 49 | inner = get_env(match.group(1)) |
| 50 | value = value.replace("${" + match.group(1) + "}", inner) |
demx8as6 | b5ec030 | 2023-07-06 14:09:11 +0000 | [diff] [blame] | 51 | return value |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 52 | |
| 53 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 54 | def loadArgs(args: List[str]) -> tuple: |
| 55 | realmFile = os.path.dirname(os.path.abspath(__file__)) + '/o-ran-sc-realm.json' |
| 56 | authFile = os.path.dirname(os.path.abspath(__file__)) + '/authentication.json' |
| 57 | readyTimeout = 180 |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 58 | args.pop(0) |
| 59 | while len(args) > 0: |
| 60 | arg = args.pop(0) |
| 61 | if arg == '--auth' and len(args) > 0: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 62 | authFile = args.pop(0) |
| 63 | print('overwriting auth file: {}'.format(authFile)) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 64 | elif arg == '--realm' and len(args) > 0: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 65 | realmFile = args.pop(0) |
| 66 | print('overwriting realm file: {}'.format(realmFile)) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 67 | elif arg == '--timeout' and len(args) > 0: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 68 | readyTimeout = int(args.pop(0)) |
| 69 | print('waiting for ready {} seconds'.format(readyTimeout)) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 70 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 71 | return (realmFile, authFile, readyTimeout) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 72 | |
| 73 | |
| 74 | def isReady(timeoutSeconds=180): |
demx8as6 | cd17882 | 2023-07-01 13:44:31 +0000 | [diff] [blame] | 75 | url = getBaseUrl() |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 76 | print(url) |
| 77 | response = None |
| 78 | print("waiting for ready state", end='') |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 79 | while timeoutSeconds > 0: |
| 80 | try: |
| 81 | response = requests.get(url, verify=False, headers={}) |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 82 | print(response) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 83 | except: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 84 | pass |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 85 | if response is not None and response.status_code == 200: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 86 | print('succeeded') |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 87 | return True |
| 88 | time.sleep(1) |
| 89 | timeoutSeconds -= 1 |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 90 | print('.', end='', flush=True) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 91 | return False |
| 92 | |
| 93 | |
| 94 | def getBaseUrl(): |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 95 | try: |
| 96 | if get_env("USE_LOCAL_HOST_FOR_IDENTITY_CONFIG").strip("'\"") == "true": |
| 97 | return get_env("IDENTITY_PROVIDER_URL_LOCAL_HOST") |
| 98 | except AttributeError: |
| 99 | print("Using IDENTITY_PROVIDER_URL") |
| 100 | return get_env("IDENTITY_PROVIDER_URL") |
demx8as6 | cd17882 | 2023-07-01 13:44:31 +0000 | [diff] [blame] | 101 | |
| 102 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 103 | # Request a token for futher communication |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 104 | def getToken(): |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 105 | url = base + '/realms/master/protocol/openid-connect/token' |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 106 | headers = { |
| 107 | 'content-type': 'application/x-www-form-urlencoded', |
| 108 | 'accept': 'application/json' |
| 109 | } |
| 110 | body = { |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 111 | 'client_id': 'admin-cli', |
| 112 | 'grant_type': 'password', |
| 113 | 'username': username, |
| 114 | 'password': password |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 115 | } |
| 116 | try: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 117 | response = requests.post(url, verify=False, auth=(username, password), data=body, headers=headers) |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 118 | except requests.exceptions.Timeout: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 119 | sys.exit('HTTP request failed, please check you internet connection.') |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 120 | except requests.exceptions.TooManyRedirects: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 121 | sys.exit('HTTP request failed, please check your proxy settings.') |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 122 | except requests.exceptions.RequestException as e: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 123 | # catastrophic error. bail. |
| 124 | raise SystemExit(e) |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 125 | |
| 126 | if response.status_code >= 200 and response.status_code < 300: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 127 | print('Got token!') |
| 128 | return response.json()['access_token'] |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 129 | else: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 130 | sys.exit('Getting token failed.') |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 131 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 132 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 133 | # create the default realm from file |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 134 | def createRealm(token, realm): |
| 135 | url = base + '/admin/realms' |
| 136 | auth = 'bearer ' + token |
| 137 | headers = { |
| 138 | 'content-type': 'application/json', |
| 139 | 'accept': 'application/json', |
| 140 | 'authorization': auth |
| 141 | } |
| 142 | try: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 143 | response = requests.post(url, verify=False, json=realm, headers=headers) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 144 | except requests.exceptions.Timeout: |
| 145 | sys.exit('HTTP request failed, please check you internet connection.') |
| 146 | except requests.exceptions.TooManyRedirects: |
| 147 | sys.exit('HTTP request failed, please check your proxy settings.') |
| 148 | except requests.exceptions.RequestException as e: |
| 149 | # catastrophic error. bail. |
| 150 | raise SystemExit(e) |
| 151 | |
| 152 | return response.status_code >= 200 and response.status_code < 300 |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 153 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 154 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 155 | # Check if default realm exists |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 156 | def checkRealmExists(token, realmId): |
| 157 | url = base + '/admin/realms/' + realmId |
| 158 | auth = 'bearer ' + token |
| 159 | headers = { |
| 160 | 'accept': 'application/json', |
| 161 | 'authorization': auth |
| 162 | } |
| 163 | try: |
| 164 | response = requests.get(url, verify=False, headers=headers) |
| 165 | except requests.exceptions.Timeout: |
| 166 | sys.exit('HTTP request failed, please check you internet connection.') |
| 167 | except requests.exceptions.TooManyRedirects: |
| 168 | sys.exit('HTTP request failed, please check your proxy settings.') |
| 169 | except requests.exceptions.RequestException as e: |
| 170 | # catastrophic error. bail. |
| 171 | raise SystemExit(e) |
| 172 | |
| 173 | if response.status_code >= 200 and response.status_code < 300: |
| 174 | return realmId == response.json()['id'] |
| 175 | else: |
| 176 | # sys.exit('Getting realm failed.') |
| 177 | return False |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 178 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 179 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 180 | # create a user in default realm |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 181 | def createUser(token, realmConfig, user): |
| 182 | realmId = realmConfig['id'] |
| 183 | url = base + '/admin/realms/' + realmId + '/users' |
| 184 | auth = 'bearer ' + token |
| 185 | headers = { |
| 186 | 'accept': 'application/json', |
| 187 | 'authorization': auth |
| 188 | } |
| 189 | try: |
| 190 | response = requests.post(url, verify=False, json=user, headers=headers) |
| 191 | except requests.exceptions.Timeout: |
| 192 | sys.exit('HTTP request failed, please check you internet connection.') |
| 193 | except requests.exceptions.TooManyRedirects: |
| 194 | sys.exit('HTTP request failed, please check your proxy settings.') |
| 195 | except requests.exceptions.RequestException as e: |
| 196 | # catastrophic error. bail. |
| 197 | raise SystemExit(e) |
| 198 | |
| 199 | if response.status_code >= 200 and response.status_code < 300: |
| 200 | print('User', user['username'], 'created!') |
| 201 | else: |
| 202 | print('User creation', user['username'], 'failed!\n', response.text) |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 203 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 204 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 205 | # creates User accounts in realm based a file |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 206 | def createUsers(token, realmConfig, authConfig): |
| 207 | for user in authConfig['users']: |
| 208 | createUser(token, realmConfig, user) |
| 209 | |
| 210 | # create a user based on system user |
| 211 | systemUser = { |
| 212 | "firstName": getpass.getuser(), |
| 213 | "lastName": "", |
| 214 | "email": getpass.getuser() + "@sdnr.onap.org", |
| 215 | "enabled": "true", |
| 216 | "username": getpass.getuser(), |
| 217 | "credentials": [ |
| 218 | { |
| 219 | "type": "password", |
| 220 | "value": password, |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 221 | "temporary": False |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 222 | } |
demx8as6 | cd17882 | 2023-07-01 13:44:31 +0000 | [diff] [blame] | 223 | ] |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 224 | } |
| 225 | createUser(token, realmConfig, systemUser) |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 226 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 227 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 228 | # Grants a role to a user |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 229 | def addUserRole(user: dict, role: list, options: dict): |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 230 | url = options['url'] + '/' + user['id'] + '/role-mappings/realm' |
| 231 | try: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 232 | for irole in role: |
| 233 | response = requests.post(url, verify=False, json=[{'id': irole['id'], 'name': irole['name']}], |
| 234 | headers=options['headers']) |
| 235 | if response.status_code >= 200 and response.status_code < 300: |
| 236 | print('User role', user['username'], irole['name'], 'created!') |
| 237 | else: |
| 238 | print('Creation of user role', user['username'], irole['name'], 'failed!\n', response.text) |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 239 | except requests.exceptions.Timeout: |
| 240 | sys.exit('HTTP request failed, please check you internet connection.') |
| 241 | except requests.exceptions.TooManyRedirects: |
| 242 | sys.exit('HTTP request failed, please check your proxy settings.') |
| 243 | except requests.exceptions.RequestException as e: |
| 244 | # catastrophic error. bail. |
| 245 | raise SystemExit(e) |
| 246 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 247 | |
| 248 | # searches for the role of a given user |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 249 | def findRole(username: str, authConfig: dict, realmConfig: dict) -> dict: |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 250 | roleList = [] |
| 251 | roleNames = [] |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 252 | roleName = 'administration' |
| 253 | for grant in authConfig['grants']: |
| 254 | if grant['username'] == username: |
| 255 | roleName = grant['role'] |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 256 | roleNames = roleName.split(",") # A user can have multiple roles, comma separated |
| 257 | for iroleName in roleNames: |
| 258 | for role in realmConfig['roles']['realm']: |
| 259 | if role['name'] == iroleName: |
| 260 | roleList.append(role) |
| 261 | return roleList |
| 262 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 263 | |
| 264 | # adds roles to users |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 265 | def addUserRoles(token, realmConfig, authConfig): |
| 266 | realmId = realmConfig['id'] |
| 267 | url = base + '/admin/realms/' + realmId + '/users' |
| 268 | auth = 'bearer ' + token |
| 269 | headers = { |
| 270 | 'content-type': 'application/json', |
| 271 | 'accept': 'application/json', |
| 272 | 'authorization': auth |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 273 | } |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 274 | try: |
| 275 | response = requests.get(url, verify=False, headers=headers) |
| 276 | except requests.exceptions.Timeout: |
| 277 | sys.exit('HTTP request failed, please check you internet connection.') |
| 278 | except requests.exceptions.TooManyRedirects: |
| 279 | sys.exit('HTTP request failed, please check your proxy settings.') |
| 280 | except requests.exceptions.RequestException as e: |
| 281 | # catastrophic error. bail. |
| 282 | raise SystemExit(e) |
| 283 | |
| 284 | if response.status_code >= 200 and response.status_code < 300: |
| 285 | users = response.json() |
| 286 | options = { |
| 287 | "url": url, |
| 288 | "auth": auth, |
| 289 | "headers": headers |
| 290 | } |
| 291 | for user in users: |
| 292 | role = findRole(user['username'], authConfig, realmConfig) |
| 293 | addUserRole(user, role, options) |
| 294 | else: |
| 295 | sys.exit('Getting users failed.') |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 296 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 297 | |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 298 | # main |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 299 | |
demskeq8 | ef65699 | 2024-03-20 08:50:14 +0100 | [diff] [blame^] | 300 | (realmFile, authFile, readyTimeout) = loadArgs(sys.argv) |
| 301 | username = get_env('ADMIN_USERNAME') |
| 302 | password = get_env('ADMIN_PASSWORD') |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 303 | base = getBaseUrl() |
| 304 | isReady(readyTimeout) |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 305 | token = getToken() |
| 306 | if token: |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 307 | with open(realmFile) as file: |
| 308 | realmConfig = json.load(file) |
| 309 | if not checkRealmExists(token, realmConfig['id']): |
| 310 | createRealm(token, realmConfig) |
demx8as6 | a93cb37 | 2021-06-06 16:05:58 +0200 | [diff] [blame] | 311 | |
demx8as6 | f81ad30 | 2022-09-15 14:19:17 +0200 | [diff] [blame] | 312 | with open(authFile) as authConfig: |
| 313 | authConfig = json.load(authConfig) |
| 314 | createUsers(token, realmConfig, authConfig) |
| 315 | addUserRoles(token, realmConfig, authConfig) |
| 316 | exit(0) |
| 317 | exit(1) |