Example #1
0
    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')
Example #4
0
    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")
Example #6
0
    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")
Example #7
0
    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")
Example #8
0
    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")
Example #9
0
    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")
Example #10
0
    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")
Example #11
0
    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")
Example #12
0
    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")
Example #13
0
 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
Example #14
0
    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)
Example #15
0
    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")
Example #16
0
    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
Example #18
0
    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
Example #19
0
 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')
Example #21
0
    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)