def verify(self, tenant: AnyStr, password: AnyStr) -> bool: """ Verify a user using LDAP schema. :tenant (AnyStr) the user username :password (AnyStr) the user password Return a boolean describing the success of the user authentication. """ result = None try: if tenant == envvar('ADMIN_ACCOUNT'): if password == envvar('GRAFANA_ADMIN_PASSWORD'): result = True else: result = False else: result = self.client.compare_s( 'cn={},{}'.format(tenant, self.l_schema), 'userPassword', password) if result and envvar('GRAFANA') == 'true': user_grafana = grafana.get_grafana_user(tenant) if not user_grafana: grafana.create_grafana_user(tenant, password) if self.verify_group_admin(): grafana.update_grafana_role(user_grafana, 'Editor') except ldap.NO_SUCH_OBJECT: logging.error(f'User does not exist {tenant}') finally: return result
def update_frontend_rating_config() -> Response: """Edit a configuration, from the frontend.""" received = load_from_form(request.form) try: schema.validate_request_content(received) except schema.ValidationError as exc: abort(make_response(jsonify(message=exc.message), 400)) try: api = client.CustomObjectsApi(get_client()) cr = api.get_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrules', 'namespace': envvar('RATING_NAMESPACE'), 'name': received['name'] }) cr['spec'] = { 'metrics': received.get('metrics', cr['spec']['metrics']), 'rules': received.get('rules', cr['spec']['rules']) } api.patch_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrules', 'namespace': envvar('RATING_NAMESPACE'), 'name': received['name'], 'body': cr }) except ApiException as exc: abort(make_response(str(exc), 400)) return make_response(f'RatingRule {received["name"]} edited', 200)
def new_frontend_rating_config() -> Response: """Add a new configuration, from the frontend.""" received = load_from_form(request.form) try: schema.validate_request_content(received) except schema.ValidationError as exc: abort(make_response(jsonify(message=exc.message), 400)) body = { 'apiVersion': 'rating.alterway.fr/v1', 'kind': 'RatingRule', 'metadata': { 'name': received['name'], 'namespace': envvar('RATING_NAMESPACE') }, 'spec': { 'metrics': received['metrics'], 'rules': received['rules'] } } try: api = client.CustomObjectsApi(get_client()) api.create_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'namespace': envvar('RATING_NAMESPACE'), 'plural': 'ratingrules', 'body': body }) except ApiException as exc: abort(make_response(str(exc), 400)) return make_response(f'RatingRule {received["body"]["name"]} created', 200)
def models_rule_edit() -> Response: """Edit a RatingRuleModels.""" config = request.form or request.get_json() api = client.CustomObjectsApi(get_client()) try: cr = api.get_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrulemodels', 'namespace': envvar('RATING_NAMESPACE'), 'name': config['name'] }) cr['spec'] = { 'metric': config.get('metric', cr['spec']['metric']), 'timeframe': config.get('timeframe', cr['spec']['timeframe']), 'name': config.get('metric_name', cr['spec']['name']) } api.patch_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrulemodels', 'namespace': envvar('RATING_NAMESPACE'), 'name': config['name'], 'body': cr }) except ApiException as exc: abort(make_response(str(exc), 400)) return make_response(f'RatingRuleModels {config["name"]} edited', 200)
def change_password(tenant: AnyStr) -> Response: """ Change the password of the user. :tenant (AnyStr) A string representing the tenant. Return a response object. """ old, new = request.form['old'], request.form['new'] if tenant == '' or old == new: return make_response( render_template('password.html', message='New and old password are similar')) elif auth.verify(tenant, old): if envvar('GRAFANA') == 'true': grafana.update_grafana_password(grafana.get_grafana_user(tenant), password) query.update_tenant(tenant, encrypt_password(new)) return make_response( render_template('password.html', message='Your password has been updated')) else: return make_response( render_template('password.html', message='unrecognized user / password'))
def verify(self, tenant: AnyStr, password: AnyStr) -> bool: """ Verify if a user token is not empty. :tenant (AnyStr) the user username :password (AnyStr) the user password Return a boolean describing the success of the user verification in keycloak. """ if tenant == envvar('ADMIN_ACCOUNT'): if password == envvar('GRAFANA_ADMIN_PASSWORD'): return True else: return False else: return self.get_token(tenant, password)
def models_rule_new() -> Response: """Create a RatingRuleModels.""" config = request.form or request.get_json() body = { 'apiVersion': 'rating.alterway.fr/v1', 'kind': 'RatingRuleModels', 'metadata': { 'name': config['name'] }, 'spec': { 'timeframe': config['timeframe'], 'metric': config['metric'], 'name': config['metric_name'] } } api = client.CustomObjectsApi(get_client()) try: api.create_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'namespace': envvar('RATING_NAMESPACE'), 'plural': 'ratingrulemodels', 'body': body }) except ApiException as exc: abort(make_response(str(exc), 400)) return make_response(f'RatingRuleModels {config["name"]} created', 200)
def update_postgres_schema(): """Verify and update the postgreSQL schema.""" postgres_database_uri = config.envvar('POSTGRES_DATABASE_URI') engine = create_engine(postgres_database_uri) global db_updated if not db_updated: db_update(engine) db_updated = True
def initialize_ldap_connection(self) -> Dict: """Initialize the ldap connection.""" l_con = None try: l_con = ldap.initialize(envvar('LDAP_URL')) except ldap.LDAPError: logging.error('Wrong LDAP URL') finally: return l_con
def presto_engine(): """ Initialize the Presto database engine. Returns the created engine """ sqlalchemy.dialects.presto = sqlalchemy_presto presto_database_uri = config.envvar('PRESTO_DATABASE_URI') return create_engine(presto_database_uri)
def signup_user() -> Response: """Create a user and the associated namespaces.""" tenant = request.form.get('tenant') password = request.form.get('password') admin_user = request.form.get('admin') namespaces = request.form.get('namespaces') if new_user(tenant, password) and envvar('ADMIN_ACCOUNT') != tenant: if admin_user == 'on': query.insert_group_tenant(tenant, 'admin') else: query.insert_group_tenant(tenant, 'user') add_user(tenant, password) update_tenant_namespaces(tenant, namespaces) if envvar('GRAFANA') == 'true': grafana.create_grafana_user(tenant, password) if admin_user == 'on': grafana.update_grafana_role(grafana.get_grafana_user(tenant), 'Editor') return render_template('signup.html', message='User created') return render_template('signup.html', message='User already exists')
def format_grafana_frontend_request(url: AnyStr) -> AnyStr: """ Format the URL for a frontend request toward Grafana. :url (AnyStr) A string representing the destination of the request. Return the formatted url. """ protocol = 'https' if os.environ.get('AUTH', 'false') == 'true' else 'http' grafana_frontend_url = envvar('FRONTEND_URL') return f'{protocol}://{grafana_frontend_url}{url}'
def check_admin(tenant: AnyStr) -> bool: """ Check if a user is admin. tenant: (AnyStr) the username Return a boolean to express if a user is admin or no. """ if tenant == envvar('ADMIN_ACCOUNT'): return True else: return auth.verify_group_admin(tenant=tenant)
def client(self, **kwargs: Dict) -> KeycloakOpenID: """ Create an authenticated keycloak client. :kwargs (Dict) A directory contaning the keycloak client authentication credentials Return the KeycloakOpenID object. """ config = {} if kwargs: config.update(kwargs) else: config.update({ 'server_url': envvar('KEYCLOAK_URL'), 'client_id': envvar('KEYCLOAK_CLIENT_ID'), 'realm_name': envvar('KEYCLOAK_REALM'), 'client_secret_key': envvar('KEYCLOAK_SECRET_KEY') }) self.client = KeycloakOpenID(**config) return self.client
def verify(self, tenant: AnyStr, password: AnyStr) -> bool: """ Verify a user in local database. :tenant (AnyStr) the user username :password (AnyStr) the user password Return a boolean describing the success of the user authentication. """ if tenant == envvar('ADMIN_ACCOUNT'): if password == envvar('GRAFANA_ADMIN_PASSWORD'): return True else: return False else: results = query.get_tenant_id(tenant) if not results or \ not check_encrypted_password(password, results[0]['password']): return False else: return True
def login_user(): """ Login the user into rating operator. Return the user session if the credentials are valid or an error message if not. """ tenant = request.form.get('tenant') password = request.form.get('password') verified = auth.verify(tenant, password) if verified: logging.info('User logged') session.update({ 'tenant': tenant, 'timestamp': time.time(), }) cookie_settings = {} if tenant != envvar_string('ADMIN_ACCOUNT') and hasattr( auth, 'instance'): namespaces = auth.get_namespace() update_tenant_namespaces(tenant, namespaces) if isinstance(auth.instance, Keycloak): session.update({'token': verified}) cookie_settings.update({ 'httponly': envvar_string('COOKIE_HTTPONLY'), 'secure': envvar_string('COOKIE_SECURE'), 'samesite': envvar_string('COOKIE_SAMESITE') }) if os.environ.get('AUTH') == 'true': cookie_settings.update( {'domain': os.environ.get('DOMAIN')}) response = make_response(redirect('/home')) # protocol = 'https' if os.environ.get('AUTH', 'false') == 'true' else 'http' # params = { # '_scheme': protocol, # '_external': protocol == 'https' # } # to = url_for('.dashboards', **params) # response = make_response(redirect(to)) if envvar('GRAFANA') == 'true': grafana_session = grafana.login_grafana_user(tenant, password) if grafana_session: response.set_cookie('grafana_session', grafana_session, **cookie_settings) return response else: message = 'Invalid credentials/Authentication server unreachable' return render_template('login.html', message=message)
def wrapper(**kwargs: dict) -> Callable: """ Execute the verification of the admin token. :kwargs (dict) A dictionary containing all the function parameters Return the decorated function """ token = get_token_from_request(request) admin_api_key = envvar('RATING_ADMIN_API_KEY') if token == admin_api_key: return func(**kwargs) raise InvalidToken('Internal token unrecognized')
def format_grafana_admin_request(url: AnyStr) -> AnyStr: """ Format the URL for an administrator request toward Grafana. :url (AnyStr) A string representing the destination of the request. Return the formatted URL. """ admin_password = envvar('GRAFANA_ADMIN_PASSWORD') admin_name = os.environ.get('ADMIN_ACCOUNT', 'admin') protocol = 'https' if os.environ.get('AUTH', 'false') == 'true' else 'http' grafana_backend_url = get_backend_url() return f'{protocol}://{admin_name}:{admin_password}@{grafana_backend_url}{url}'
def wrapper(**kwargs: Dict) -> Callable: """ Verify if super-admin and return the tenant session. :kwargs (Dict) A dictionary containing all the function parameters Return the decorated function. """ tenant = session.get('tenant') if tenant == envvar('ADMIN_ACCOUNT'): response = func(**kwargs) else: response = make_response(redirect('/login')) return response
def delete_frontend_rating_config() -> Response: """Delete a configuration, from the frontend.""" try: api = client.CustomObjectsApi(get_client()) api.delete_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'namespace': envvar('RATING_NAMESPACE'), 'plural': 'ratingrules', 'name': request.form['name'] }) except ApiException as exc: abort(make_response(str(exc), 400)) return make_response(f'RatingRule {request.form["name"]} deleted', 200)
def format_url(path: AnyStr) -> AnyStr: """ Format the url according to environment variables and path. :path (AnyStr) A string representing the path to construct. Returns the formatted url. """ if os.environ.get('AUTH') == 'true' and 'http' not in path: api_url = envvar('RATING_API_URL') domain = os.environ.get('DOMAIN', 'svc.cluster.local') protocol = 'https' if os.environ.get('AUTH', 'false') == 'true' else 'http' return f'{protocol}://{api_url}.{domain}{path}' return path
def register_admin_key(): """Register the administrator key from the environment.""" config.load_incluster_config() api = client.CoreV1Api(get_client()) namespace = envvar('RATING_NAMESPACE') secret_name = f'{namespace}-admin' try: secret_encoded_bytes = api.read_namespaced_secret( secret_name, namespace).data except ApiException as exc: raise exc rating_admin_api_key = list(secret_encoded_bytes.keys())[0] os.environ[rating_admin_api_key] = b64decode( secret_encoded_bytes[rating_admin_api_key]).decode('utf-8') return os.environ[rating_admin_api_key]
def models_rule_get() -> Response: """Get a RatingRuleModels.""" api = client.CustomObjectsApi(get_client()) try: response = api.get_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrulemodels', 'namespace': envvar('RATING_NAMESPACE'), 'name': request.args.to_dict()['name'] }) except ApiException as exc: abort(make_response(str(exc), 400)) return {'results': response, 'total': 1}
def models_rule_delete() -> Response: """Delete a RatingRuleModels.""" config = request.form or request.get_json() api = client.CustomObjectsApi(get_client()) try: api.delete_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'namespace': envvar('RATING_NAMESPACE'), 'plural': 'ratingrulemodels', 'name': config['name'] }) except ApiException as exc: abort(make_response(str(exc), 400)) return make_response(f'RatingRuleModels {config["name"]} deleted', 200)
def logout_user() -> Response: """Disconnect a user by closing the session.""" if 'tenant' not in session: abort(400) resp = make_response(redirect('/login')) if envvar('GRAFANA') == 'true': grafana.logout_grafana_user(session['tenant']) if os.environ.get('AUTH') == 'true': resp.delete_cookie('grafana_session', domain=os.environ.get('DOMAIN')) else: resp.delete_cookie('grafana_session') if session.get('token') is not None: auth.client().logout(session['token']['refresh_token']) session.clear() return resp
def models_rule_list() -> Response: """Get the RatingRuleModel.""" try: api = client.CustomObjectsApi(get_client()) response = api.list_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrulemodels', 'namespace': envvar('RATING_NAMESPACE') }) except ApiException as exc: abort(make_response(str(exc), 400)) return { 'results': [item['metadata']['name'] for item in response['items']], 'total': 1 }
def frontend_rating_config(config_name: AnyStr) -> Response: """ Get the RatingRule for a given configuration name. :config_name (AnyStr) A string representing the configuration name. Return the configuration or abort. """ try: api = client.CustomObjectsApi(get_client()) response = api.get_namespaced_custom_object( **{ 'group': 'rating.alterway.fr', 'version': 'v1', 'plural': 'ratingrules', 'namespace': envvar('RATING_NAMESPACE'), 'name': config_name }) except ApiException as exc: abort(make_response(str(exc), 400)) return {'results': response, 'total': 1}
def update_rated_metrics_object(metric: AnyStr, last_insert: AnyStr): """ Update a RatedMetrics object. :metric (AnyStr) A name representing the metric to be updated :last_insert (AnyStr) The timestamp of the latest data frame rating """ config.load_incluster_config() rated_metric = f'rated-{metric.replace("_", "-")}' rated_namespace = envvar('RATING_NAMESPACE') custom_api = client.CustomObjectsApi(get_client()) body = { 'apiVersion': 'rating.alterway.fr/v1', 'kind': 'RatedMetric', 'metadata': { 'namespace': rated_namespace, 'name': rated_metric, }, 'spec': { 'metric': metric, 'date': last_insert } } try: custom_api.create_namespaced_custom_object(group='rating.alterway.fr', version='v1', namespace=rated_namespace, plural='ratedmetrics', body=body) except ApiException as exc: if exc.status != 409: raise exc custom_api.patch_namespaced_custom_object(group='rating.alterway.fr', version='v1', namespace=rated_namespace, plural='ratedmetrics', name=rated_metric, body=body)
def get_keycloak_user_token(self, tenant: AnyStr, password: AnyStr) -> Dict: """ Get the token of the user authentication in Keycloak. :tenant (AnyStr) the user username :password (AnyStr) the user password Return the keycloak user token if valid credentials. """ keycloak_openid = self.client token = None try: token = keycloak_openid.token(tenant, password) if envvar('GRAFANA') == 'true': user_grafana = grafana.get_grafana_user(tenant) if not user_grafana: grafana.create_grafana_user(tenant, password) if self.verify_group_admin(): grafana.update_grafana_role(user_grafana, 'Editor') except exceptions.KeycloakAuthenticationError or exceptions.KeycloakGetError: logging.error(f'Authentication error for user {tenant}') finally: return token
Verify if a user is admin. :kwargs (Dict) contains username Return a boolean describing if the user is in admin group. """ if kwargs['tenant']: tenant = kwargs['tenant'] res = query.get_group_tenant(tenant) if res[0]['user_group'] == 'admin': return True else: return False def __init__(self, method: AnyStr = None, **kwargs): try: instance = {'ldap': LDAP, 'keycloak': Keycloak}[method]() except KeyError: return self.instance = instance self.client = instance.client self.verify = instance.verify self.get_namespace = instance.get_namespace self.verify_group_admin = instance.verify_group_admin # envvar('AUTH_METHOD')) auth = Authenticator(method=envvar('AUTH_METHOD')) auth.client()