def admin(self, env, data): if len(self._userdict) == 0: if env.get('REQUEST_METHOD') == 'POST': decoded = dict((k, v if len(v) > 1 else v[0]) for k, v in parse_qs(data).items()) username = decoded.get('username') pass1 = decoded.get('password1') pass2 = decoded.get('password2') if pass1 == pass2 and pass1 is not None: _log.debug("Setting administrator password") self.add_user(username, pass1, groups=['admin']) return Response('', status='302', headers={'Location': '/admin/login.html'}) template = template_env(env).get_template('first.html') return Response(template.render(), content_type="text/html") if 'login.html' in env.get('PATH_INFO') or '/admin/' == env.get( 'PATH_INFO'): template = template_env(env).get_template('login.html') _log.debug("Login.html: {}".format(env.get('PATH_INFO'))) return Response(template.render(), content_type='text/html') return self.verify_and_dispatch(env, data)
def get_auth_tokens(self, env, data): """ Creates an authentication refresh and acccss tokens to be returned to the caller. The response will be a text/plain encoded user. Data should contain: { "username": "******", "password": "******" } :param env: :param data: :return: """ assert len( self._userdict ) > 0, "No users in user dictionary, set the administrator password first!" if not isinstance(data, dict): _log.debug("data is not a dict, decoding") decoded = dict((k, v if len(v) > 1 else v[0]) for k, v in parse_qs(data).items()) username = decoded.get('username') password = decoded.get('password') else: username = data.get('username') password = data.get('password') _log.debug("Username is: {}".format(username)) error = "" if username is None: error += "Invalid username passed" if not password: error += "Invalid password passed" if error: _log.error("Invalid parameters passed: {}".format(error)) return Response(error, status='401') user = self.__get_user(username, password) if user is None: _log.error( "No matching user for passed username: {}".format(username)) return Response('', status='401') access_token, refresh_token = self._get_tokens(user) response = Response(json.dumps({ "refresh_token": refresh_token, "access_token": access_token }), content_type="application/json") return response
def get_auth_token(self, env, data): """ Creates an authentication token to be returned to the caller. The response will be a text/plain encoded user :param env: :param data: :return: """ if env.get('REQUEST_METHOD') != 'POST': _log.warning("Authentication must use POST request.") return Response('', status='401 Unauthorized') assert len( self._userdict ) > 0, "No users in user dictionary, set the master password first!" if not isinstance(data, dict): _log.debug("data is not a dict, decoding") decoded = dict((k, v if len(v) > 1 else v[0]) for k, v in urlparse.parse_qs(data).iteritems()) username = decoded.get('username') password = decoded.get('password') else: username = data.get('username') password = data.get('password') _log.debug("Username is: {}".format(username)) error = "" if username is None: error += "Invalid username passed" if not password: error += "Invalid password passed" if error: _log.error("Invalid parameters passed: {}".format(error)) return Response(error, status='401') user = self.__get_user(username, password) if user is None: _log.error( "No matching user for passed username: {}".format(username)) return Response('', status='401') encoded = jwt.encode(user, self._ssl_private_key, algorithm='RS256').encode('utf-8') return Response(encoded, '200 OK', content_type='text/plain')
def __cert_list_api(self): subjects = [ dict(common_name=x.common_name) for x in self._certs.get_all_cert_subjects() ] return Response(json.dumps(subjects), content_type="application/json")
def renew_auth_token(self, env, data): """ Creates a new authentication access token to be returned to the caller. The response will be a text/plain encoded user. Request should contain: • Content Type: application/json • Authorization: BEARER <jwt_refresh_token> • Body (optional): { "current_access_token": "<jwt_access_token>" } :param env: :param data: :return: """ current_access_token = data.get('current_access_token') from volttron.platform.web import get_bearer, get_user_claim_from_bearer, NotAuthorized try: current_refresh_token = get_bearer(env) claims = get_user_claim_from_bearer( current_refresh_token, web_secret_key=self._web_secret_key, tls_public_key=self._tls_public_key) except NotAuthorized: _log.error("Unauthorized user attempted to connect to {}".format( env.get('PATH_INFO'))) return Response('Unauthorized User', status="401 Unauthorized") except jwt.ExpiredSignatureError: _log.error( "User attempted to connect to {} with an expired signature". format(env.get('PATH_INFO'))) return Response('Unauthorized User', status="401 Unauthorized") if claims.get('grant_type') != 'refresh_token' or not claims.get( 'groups'): return Response('Invalid refresh token.', status="401 Unauthorized") else: # TODO: Consider blacklisting and reissuing refresh tokens also when used. new_access_token, _ = self._get_tokens(claims) if current_access_token: pass # TODO: keep current subscriptions? blacklist old token? return Response(json.dumps({"access_token": new_access_token}), content_type="application/json")
def __pending_csrs_api(self): try: data = self._rpc_caller.call(AUTH, 'get_pending_csrs').get(timeout=4) except TimeoutError as e: data = dict(status="ERROR", message=e.message) return Response(jsonapi.dumps(data), content_type="application/json")
def __deny_credential_api(self, user_id): try: self._rpc_caller.call(AUTH, 'deny_authorization_failure', user_id) data = dict(status="DENIED", message="The administrator has denied the request") except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(jsonapi.dumps(data), content_type="application/json")
def __delete_csr_api(self, common_name): try: self._certs.delete_csr(common_name) data = dict(status="DELETED", message="The administrator has denied the request") except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(json.dumps(data), content_type="application/json")
def __cert_list_api(self): try: data = [ dict(common_name=x.common_name) for x in self._rpc_caller.call( AUTH, "get_all_pending_csr_subjects").get(timeout=2) ] except TimeoutError as e: data = dict(status="ERROR", message=e.message) return Response(jsonapi.dumps(data), content_type="application/json")
def __approve_credential_api(self, user_id): try: _log.debug( "Creating credential and permissions for user: {}".format( user_id)) self._rpc_caller.call(AUTH, 'approve_authorization_failure', user_id).wait() data = dict(status='APPROVED', message="The administrator has approved the request") except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(jsonapi.dumps(data), content_type="application/json")
def __delete_csr_api(self, common_name): try: self._rpc_caller.call(AUTH, 'delete_authorization_failure', common_name).wait(timeout=2) data = dict(status="DELETED", message="The administrator has denied the request") except ValueError as e: data = dict(status="ERROR", message=e.message) except TimeoutError as e: data = dict(status="ERROR", message=e.message) return Response(jsonapi.dumps(data), content_type="application/json")
def __approve_csr_api(self, common_name): try: _log.debug("Creating cert and permissions for user: {}".format( common_name)) self._certs.approve_csr(common_name) permissions = self._rmq_mgmt.get_default_permissions(common_name) self._rmq_mgmt.create_user_with_permissions( common_name, permissions, True) data = dict(status=self._certs.get_csr_status(common_name), cert=self._certs.get_cert_from_csr(common_name)) except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(json.dumps(data), content_type="application/json")
def __api_endpoint(self, endpoint, data): _log.debug("Doing admin endpoint {}".format(endpoint)) if endpoint == 'certs': response = self.__cert_list_api() elif endpoint == 'pending_csrs': response = self.__pending_csrs_api() elif endpoint.startswith('approve_csr/'): response = self.__approve_csr_api(endpoint.split('/')[1]) elif endpoint.startswith('deny_csr/'): response = self.__deny_csr_api(endpoint.split('/')[1]) elif endpoint.startswith('delete_csr/'): response = self.__delete_csr_api(endpoint.split('/')[1]) else: response = Response( '{"status": "Unknown endpoint {}"}'.format(endpoint), content_type="application/json") return response
def verify_and_dispatch(self, env, data): """ Verify that the user is an admin and dispatch :param env: web environment :param data: data associated with a web form or json/xml request data :return: Response object. """ from volttron.platform.web import get_user_claims, NotAuthorized try: claims = get_user_claims(env) except NotAuthorized: _log.error("Unauthorized user attempted to connect to {}".format( env.get('PATH_INFO'))) return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") # Make sure we have only admins for viewing this. if 'admin' not in claims.get('groups'): return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") # Make sure we have only admins for viewing this. if 'admin' not in claims.get('groups'): return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") path_info = env.get('PATH_INFO') if path_info.startswith('/admin/api/'): return self.__api_endpoint(path_info[len('/admin/api/'):], data) if path_info.endswith('html'): page = path_info.split('/')[-1] try: template = template_env(env).get_template(page) except TemplateNotFound: return Response("<h1>404 Not Found</h1>", status="404 Not Found") if page == 'list_certs.html': html = template.render( certs=self._certs.get_all_cert_subjects()) elif page == 'pending_csrs.html': html = template.render( csrs=self._certs.get_pending_csr_requests()) else: # A template with no params. html = template.render() return Response(html) template = template_env(env).get_template('index.html') resp = template.render() return Response(resp)
def __approve_csr_api(self, common_name): try: _log.debug("Creating cert and permissions for user: {}".format( common_name)) self._rpc_caller.call(AUTH, 'approve_authorization_failure', common_name).wait(timeout=4) data = dict( status=self._rpc_caller.call(AUTH, "get_pending_csr_status", common_name).get(timeout=2), cert=self._rpc_caller.call(AUTH, "get_pending_csr_cert", common_name).get(timeout=2)) except ValueError as e: data = dict(status="ERROR", message=e.message) except TimeoutError as e: data = dict(status="ERROR", message=e.message) return Response(jsonapi.dumps(data), content_type="application/json")
def _get_discovery(self, environ, start_response, data=None): q = query.Query(self.core) self.instance_name = q.query('instance-name').get(timeout=60) addreses = q.query('addresses').get(timeout=60) external_vip = None for x in addreses: try: if not is_ip_private(x): external_vip = x break except IndexError: pass return_dict = {} if external_vip and self.serverkey: return_dict['serverkey'] = encode_key(self.serverkey) return_dict['vip-address'] = external_vip if self.instance_name: return_dict['instance-name'] = self.instance_name if self.core.messagebus == 'rmq': config = RMQConfig() rmq_address = None if config.is_ssl: rmq_address = "amqps://{host}:{port}/{vhost}".format( host=config.hostname, port=config.amqp_port_ssl, vhost=config.virtual_host) else: rmq_address = "amqp://{host}:{port}/{vhost}".format( host=config.hostname, port=config.amqp_port, vhost=config.virtual_host) return_dict['rmq-address'] = rmq_address return_dict['rmq-ca-cert'] = self._certs.cert( self._certs.root_ca_name).public_bytes( serialization.Encoding.PEM) return Response(jsonapi.dumps(return_dict), content_type="application/json")
def handle_authenticate(self, env, data): """ Callback for /authenticate endpoint. Routes request based on HTTP method and returns a text/plain encoded token or error. :param env: :param data: :return: Response """ method = env.get('REQUEST_METHOD') if method == 'POST': response = self.get_auth_tokens(env, data) elif method == 'PUT': response = self.renew_auth_token(env, data) elif method == 'DELETE': response = self.revoke_auth_token(env, data) else: error = f"/authenticate endpoint accepts only POST, PUT, or DELETE methods. Received: {method}" _log.warning(error) return Response(error, status='405 Method Not Allowed', content_type='text/plain') return response
def _csr_request_new(self, env, data): _log.debug("New csr request") if not isinstance(data, dict): try: request_data = jsonapi.loads(data) except: _log.error( "Invalid data for csr request. Must be json serializable") return Response() else: request_data = data.copy() csr = request_data.get('csr').encode("utf-8") identity = self._certs.get_csr_common_name(csr) # The identity must start with the current instances name or it is a failure. if not identity.startswith(get_platform_instance_name() + "."): json_response = dict( status="ERROR", message="CSR must start with instance name: {}".format( get_platform_instance_name())) Response(jsonapi.dumps(json_response), content_type='application/json', headers={'Content-type': 'application/json'}) csr_file = self._certs.save_pending_csr_request( env.get('REMOTE_ADDR'), identity, csr) if self._auto_allow_csr: _log.debug( "Creating cert and permissions for user: {}".format(identity)) status = self._certs.get_csr_status(identity) json_response = dict(status=status) if status == 'APPROVED': try: json_response['cert'] = self._certs.get_cert_from_csr( identity) except Exception as e: _log.error(f"Exception getting cert from csr {e}") else: try: cert = self._certs.approve_csr(identity) #permissions = self._core().rmq_mgmt.get_default_permissions(identity) _log.debug(f"CREATING NEW RMQ USER: {identity}") permissions = dict(configure=".*", read=".*", write=".*") self._core().rmq_mgmt.create_user_with_permissions( identity, permissions, True) json_response = dict(status="SUCCESSFUL", cert=cert) except BaseException as e: _log.error( f"Exception in approving csr/creating user in auto_allow_csr mode: {e}" ) else: status = self._certs.get_csr_status(identity) cert = self._certs.get_cert_from_csr(identity) json_response = dict(status=status) if status == "APPROVED": json_response['cert'] = self._certs.get_cert_from_csr(identity) elif status == "PENDING": json_response[ 'message'] = "The request is pending admininstrator approval." elif status == "DENIED": json_response[ 'message'] = "The request has been denied by the administrator." elif status == "UNKNOWN": json_response[ 'message'] = "An unknown common name was specified to the server {}".format( identity) else: json_response[ 'message'] = "An unkonwn error has occured during the respons phase" response = None try: if json_response.get('cert', None): json_response['cert'] = json_response['cert'].decode('utf-8') response = Response(jsonapi.dumps(json_response), content_type='application/json', headers={'Content-type': 'application/json'}) except BaseException as e: _log.error(f"Exception creating Response {e}") return response
def __pending_csrs_api(self): csrs = [c for c in self._certs.get_pending_csr_requests()] return Response(json.dumps(csrs), content_type="application/json")
def revoke_auth_token(self, env, data): # TODO: Blacklist old token? Immediately close websockets? return Response('DELETE /authenticate is not yet implemented', status='501 Not Implemented', content_type='text/plain')
def verify_and_dispatch(self, env, data): """ Verify that the user is an admin and dispatch :param env: web environment :param data: data associated with a web form or json/xml request data :return: Response object. """ from volttron.platform.web import get_bearer, NotAuthorized try: claims = self._rpc_caller(PLATFORM_WEB, 'get_user_claims', get_bearer(env)).get() except NotAuthorized: _log.error("Unauthorized user attempted to connect to {}".format( env.get('PATH_INFO'))) return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") except RemoteError as e: if "ExpiredSignatureError" in e.exc_info["exc_type"]: _log.warning( "Access token has expired! Please re-login to renew.") template = template_env(env).get_template('login.html') _log.debug("Login.html: {}".format(env.get('PATH_INFO'))) return Response(template.render(), content_type='text/html') else: _log.error(e) # Make sure we have only admins for viewing this. if 'admin' not in claims.get('groups'): return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") path_info = env.get('PATH_INFO') if path_info.startswith('/admin/api/'): return self.__api_endpoint(path_info[len('/admin/api/'):], data) if path_info.endswith('html'): page = path_info.split('/')[-1] try: template = template_env(env).get_template(page) except TemplateNotFound: return Response("<h1>404 Not Found</h1>", status="404 Not Found") if page == 'pending_auth_reqs.html': try: self._pending_auths = self._rpc_caller.call( AUTH, 'get_authorization_pending').get(timeout=2) self._denied_auths = self._rpc_caller.call( AUTH, 'get_authorization_denied').get(timeout=2) self._approved_auths = self._rpc_caller.call( AUTH, 'get_authorization_approved').get(timeout=2) except TimeoutError: self._pending_auths = [] self._denied_auths = [] self._approved_auths = [] # When messagebus is rmq, include pending csrs in the output pending_auth_reqs.html page if self._rmq_mgmt is not None: html = template.render(csrs=self._rpc_caller.call( AUTH, 'get_pending_csrs').get(timeout=4), auths=self._pending_auths, denied_auths=self._denied_auths, approved_auths=self._approved_auths) else: html = template.render(auths=self._pending_auths, denied_auths=self._denied_auths, approved_auths=self._approved_auths) else: # A template with no params. html = template.render() return Response(html) template = template_env(env).get_template('index.html') resp = template.render() return Response(resp)