Exemplo n.º 1
0
    def __init__(self, api_url, api_token, verify_ssl=True):
        self.api_url = api_url
        self.api_token = api_token
        self.zones = []
        self.verify_ssl = verify_ssl
        if not self.verify_ssl:
            logger.warning(
                "__init__@dns_server.py - Disabling SSL Warnings. I hope this was intentional"
            )
            requests.packages.urllib3.disable_warnings(
                category=InsecureRequestWarning)

        payload = jwt.decode(api_token, verify=False)  # do not trust
        if not "dns_server_name" in payload.keys(
        ) or not payload["dns_server_name"]:
            logger.critical(
                f"__init__@api_client.py - No dns_server_name on api token payload: {str(payload)}"
            )
        self.dns_server_name = payload.get("dns_server_name", "")

        if not "http_server_name" in payload.keys(
        ) or not payload["http_server_name"]:
            logger.critical(
                f"__init__@api_client.py - No http_server_name on api token payload: {str(payload)}"
            )
        self.http_server_name = payload.get("http_server_name", "")
Exemplo n.º 2
0
 async def on_after_insert(mapper, connection, target):
     logger.debug("on_after_insert@DnsRequest: Publishing message")
     try:
         publisher = await make_redis()
         res = await publisher.publish_json(
             "channel:auth",
             {
                 "type": "MESSAGE",
                 "name": "DNS_REQUEST_CREATED",
                 "payload": ""
             },
         )
     except Exception as e:
         logger.warning(f"on_after_insert error: {str(e)}")
Exemplo n.º 3
0
async def store(
        form: ApiTokenCreateForm,
        api_token_repo: ApiTokenRepo = Depends(ApiTokenRepo()),
        dns_server_repo: DnsServerRepo = Depends(DnsServerRepo()),
        http_server_repo: HttpServerRepo = Depends(HttpServerRepo()),
        token: TokenPayload = Depends(ScopedTo("api-token:create")),
        user: User = Depends(current_user),
):
    if form.dns_server_id and form.dns_server_id > 0:
        dns_server_name = (dns_server_repo.first_or_fail(
            id=form.dns_server_id).results().name)
    if form.http_server_id and form.http_server_id > 0:
        http_server_name = (http_server_repo.first_or_fail(
            id=form.http_server_id).results().name)
    scopes = []
    for requested_scope in form.scopes.split(" "):
        request_scope_satisfied = False
        for user_token in token.scopes:
            # TODO: double check this, pretty lenient
            # if a:b in a:b:c
            if user_token in requested_scope:
                request_scope_satisfied = True
        if not request_scope_satisfied:
            logger.warning(
                f"[email protected]: Attempt to create unauthorized scope {requested_scope}"
            )
            raise HTTPException(403, detail="unauthorized")
        else:
            scopes.append(requested_scope)

    # TODO: use better randomness

    token = create_bearer_token(
        data={
            "sub": user.id,
            "scopes": " ".join(scopes),
            "dns_server_name": dns_server_name,
            "http_server_name": http_server_name,
        })

    data = {
        "scopes": " ".join(scopes),
        "token": str(token),
        "expires_at": form.expires_at,
        "dns_server_id": form.dns_server_id,
        "http_server_id": form.http_server_id,
    }

    api_token = api_token_repo.create(data).data()
    return ApiTokenResponse(api_token=api_token)
Exemplo n.º 4
0
    def refresh_zones_if_needed(self):
        logger.info(
            "refresh_zones_if_needed@api_client.py - Checking for New Zones and Records..."
        )
        old_zones = self.zones
        new_zones = self.get_zones()
        # TODO: fix this mess

        if len(old_zones) != len(new_zones):
            logger.warning(
                f"refresh_zones_if_needed@api_client.py - Zone Length mistmatch. New or Changed Zone Found: {str(old_zones)} != {str(new_zones)}. Reloading zones."
            )
            self.load_zones()
            return True

        for nz in new_zones:
            # make sure new zones are in old zones
            is_nz_exists = False
            for oz in old_zones:
                if oz.domain == nz.domain and oz.ip == nz.ip:
                    nz_dns_records = nz.dns_records or []
                    for nrec in nz_dns_records:
                        is_rec_satisfied = False
                        oz_dns_records = oz.dns_records or []
                        if len(nz_dns_records) != len(oz_dns_records):
                            logger.warning(
                                f"refresh_zones_if_needed@api_client.py - Zone Record Length mistmatch {str(len(nz_dns_records))} != {str(len(oz_dns_records))}. New or Changed Zone Record Found: {str(nz_dns_records)} != {str(oz_dns_records)}. Reloading zones."
                            )
                            self.load_zones()
                            return True
                        for orec in oz_dns_records:
                            if orec.record == nrec.record and orec.sort == nrec.sort:
                                is_rec_satisfied = True
                        if not is_rec_satisfied:
                            logger.warning(
                                f"refresh_zones_if_needed@api_client.py - New or Changed Zone Record {str(nz)}: {str(nrec)} found for server. Reloading zones."
                            )
                            self.load_zones()
                            return True
                    is_nz_exists = True
            if not is_nz_exists:
                logger.warning(
                    f"refresh_zones_if_needed@api_client.py - New or Changed Zone {str(nz)} found for server. Reloading zones."
                )
                self.load_zones()
                return True

        logger.info(
            "refresh_zones_if_needed@api_client.py - No New Zones or Records Found. All is well"
        )

        return False
Exemplo n.º 5
0
 def wait_for_up(self):
     attempts = 0
     while True:
         if attempts > 60:
             logger.warning("could not connect to api. api not up")
             return False
         logger.info(
             f"wait_for_up@api_client.py - checking for api status : {self.url('/status')}"
         )
         try:
             sleep(1)
             self.get_status()
             sleep(3)
             return True
         except Exception as e:
             logger.info(
                 "wait_for_up@api_client.py - api check not ready after {} attempts: {}"
                 .format(str(attempts), str(e.__class__.__name__)))
         attempts = attempts + 1
         sleep(1)
Exemplo n.º 6
0
def verify_jwt_token(token: str, bl_token_repo=None, leeway=0) -> TokenPayload:
    from boucanpy.api import config  # environment must be loaded

    if bl_token_repo:
        if bl_token_repo.exists(token=token):
            raise HTTPException(status_code=403, detail="Forbidden")
    else:
        logger.warning(
            "verifying token without checking the blacklist. dangerous!")
    try:
        payload = jwt.decode(token,
                             config.API_SECRET_KEY,
                             algorithms=config.JWT_ALGORITHM,
                             leeway=leeway)
    except jwt.PyJWTError:
        raise HTTPException(status_code=403, detail="Forbidden")

    return TokenPayload(
        payload=payload,
        scopes=payload.get("scopes", "").split(" "),
        token=token,
        sub=payload.get("sub", ""),
        exp=payload.get("exp", ""),
    )
Exemplo n.º 7
0
    async def __call__(
            self,
            request: Request,
            bl_token_repo: BlackListedTokenRepo = Depends(
                BlackListedTokenRepo()),
            token: str = Security(oauth2),
    ) -> TokenPayload:

        token = verify_jwt_token(token, bl_token_repo,
                                 self._leeway)  # proper validation goes here
        if self._satisfy not in ["all", "one"]:
            logger.warning(f"Invalid satisfy value: {self._satisfy}")

        if self._satisfy == "one":
            if not token_has_one_required_scopes(token, self._scopes):
                vmsg = f"Token does not have one of the required scopes: {str(self._scopes)}"
                logger.error(vmsg)
                abort(code=403, msg="Forbidden", debug=vmsg)
        else:
            if not token_has_required_scopes(token, self._scopes):
                vmsg = f"Token does not have all required scopes: {str(self._scopes)}"
                logger.error(vmsg)
                abort(code=403, msg="Forbidden", debug=vmsg)
        return token
Exemplo n.º 8
0
    async def run(self):
        self.db_register()
        failed = []
        if self.option("confirm"):
            for class_name, model in models.items():
                for item in self.session().query(model).all():
                    logger.warning(f"run@db_truncate.py - Deleting {item}")
                    try:
                        self.session().delete(item)
                        self.session().commit()
                    except Exception as e:
                        failed.append((item, e))
        else:
            logger.warning("run@db_truncate.py - You must confirm to drop data")

        if len(failed) > 0:
            logger.warning("run@db_truncate.py - Encountered errors")
            for f in failed:
                print("Failed:", item[0])
                print("Error", item[1])
Exemplo n.º 9
0
async def login(
        ws_access_token: bool = False,
        db: Session = Depends(async_session),
        form: OAuth2PasswordRequestForm = Depends(),
):
    username = form.username.lower() if form.username else ""

    user = db.query(User).filter_by(email=username).first()

    if not user or not user.hashed_password:
        logger.warning(f"user exists failed for {username}")
        raise HTTPException(status_code=401,
                            detail="Incorrect email or password")

    if not verify_password(form.password, user.hashed_password):
        logger.warning(f"hash verification failed for {username}")
        raise HTTPException(status_code=401,
                            detail="Incorrect email or password")

    if user.mfa_secret:  # mfa is enabled
        scopes = "profile mfa_required"
    elif user.is_superuser:
        scopes = SUPER_SCOPES  # grant access to super routes
    else:
        scopes = NORMAL_SCOPES

    logger.warning(f"creating token with scopes {scopes}")

    token = create_bearer_token(data={"sub": user.id, "scopes": scopes})
    data = {"token_type": "bearer", "access_token": str(token)}

    if ws_access_token and int(environ.get("BROADCAST_ENABLED", 0)) == 1:
        # TODO: make it so that you cannot get publish access without base scope
        data["ws_access_token"] = create_bearer_token(data={
            "sub": user.id,
            "scopes": PUBLISH_SCOPES
        })
    return PasswordAuthResponse(**data)
Exemplo n.º 10
0
    async def run(self):
        app = "boucanpy.api.main:api"
        kwargs = self.get_kwargs()

        if self.should_import_check():
            logger.info("run@api_server.py - Performing import check")
            from boucanpy.api.main import api

        logger.critical(
            "run@api_server.py - Starting api server with options: {}".format(
                str(kwargs)))
        from boucanpy.db.checks import is_db_up, is_db_setup

        # alembic just destroys the loggers, it's annoying
        if self.should_db_check():
            logger.info(
                "run@api_server.py - Waiting for database service to be up")
            db_wait_options = self._args_to_dict(self.options)
            await DbWait(db_wait_options).run()

        if self.option("db_setup"):
            logger.critical("run@api_server.py - Running database migration")
            db_setup_options = self._args_to_dict(self.options)
            if self.option("db_seed"):
                db_setup_options["seed"] = True
            await DbSetup(db_setup_options).run()

        if self.should_db_check():
            logger.info(
                "run@api_server.py - Checking if application database is setup and configured"
            )

            db_setup = is_db_setup()
            if not db_setup:
                logger.critical(
                    "run@api_server.py - Database not setup error. please check logs"
                )
                return self.exit(1)

        from boucanpy.broadcast import is_broadcast_up

        if self.should_bcast_check():
            bcast_up = await is_broadcast_up()
            if not bcast_up:
                logger.critical(
                    "run@api_server.py - Broadcast (queue) not up error. please check logs"
                )
                return self.exit(1)

        if self.option("db_seed_env", False):
            self.seed_from_env()

        # taken from uvicorn/main.py:run

        logger.debug("run@api_server.py - Building Uvicorn Config and Server")
        config = UvicornConfig(app,
                               log_config=self.get_uvicorn_logging(),
                               **kwargs)
        server = UvicornServer(config=config)
        if self.option("force_exit"):
            server.force_exit = True

        if isinstance(app, str) and (config.debug or config.reload):
            logger.warning(
                f"run@api_server.py - Running boucanpy api in dev mode...")
            sock = config.bind_socket()
            supervisor = StatReload(config)
            return supervisor.run(server.run, sockets=[sock])
        elif config.workers > 1:
            sock = config.bind_socket()
            supervisor = Multiprocess(config)
            logger.warning(
                f"run@api_server.py - Running boucanpy api in worker mode...")
            return supervisor.run(server.run, sockets=[sock])
        else:
            sockets = None
            logger.warning(
                f"run@api_server.py - Running boucanpy api in standard mode..."
            )
            return await server.serve(sockets=sockets)