Giter Club home page Giter Club logo

access's Introduction

ACCESS

Meet Access, a centralized portal for employees to transparently discover, request, and manage their access for all internal systems needed to do their jobs. If you're interested in the project, come chat with us!

Join our Discord!

Purpose

The access service exists to help answer the following questions for each persona:

  • All Users
    • What do I have access to?
    • What does a teammate have access to that I don’t?
    • What groups and roles are available?
    • Can I get access?
  • Team Leads
    • How do I give access to a new team member easily?
    • How do I give temporary access to an individual for a cross-functional effort?
    • Which roles do I administer?
    • How can I create, merge, or split a role based on a team re-org?
  • Application Owners
    • Who has access to my application?
    • How do I setup access for a new application?
    • How do I create a new access group for my application?
    • How do I give a role access to one of my application's groups?

Development Setup

Access is a React and Typescript single-page application (SPA) with a Flask API that connects to the Okta API.

You'll need an Okta API Token from an Okta user with the Group Admin and Application Admin Okta administrator roles granted as well as all Group permissions (ie. Manage groups checkbox checked) in a custom Admin role. If you want to manage Groups which grant Okta Admin permissions, then the Okta API Token will need to be created from an Okta user with the Super Admin Okta administrator role.

Flask

Create a .env file in the repo root with the following variables:

CURRENT_OKTA_USER_EMAIL=<YOUR_OKTA_USER_EMAIL>
OKTA_DOMAIN=<YOUR_OKTA_DOMAIN> # For example, "mydomain.oktapreview.com"
OKTA_API_TOKEN=<YOUR_SANDBOX_API_TOKEN>
DATABASE_URI="sqlite:///access.db"
CLIENT_ORIGIN_URL=http://localhost:3000
REACT_APP_API_SERVER_URL=http://localhost:6060

Next, run the following commands to set up your python virtual environment. Access can be run with Python 3.10 and above:

python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt

Afterwards, seed the db:

flask db upgrade
flask init <YOUR_OKTA_USER_EMAIL>

Finally, you can run the server:

flask run

Go to http://localhost:6060/api/users/ to view the API.

Node

In a separate window, setup and run nodejs:

npm install
npm start

Go to http://localhost:3000/ to view the React SPA.

Generating Typescript React-Query API Client

We use openapi-codegen to generate a Typescript React-Query v4 API Fetch Client based on our Swagger API schema available at http://localhost:6060/api/swagger.json. We've modified that generated Swagger schema in api/swagger.json, which is then used in openapi-codegen.config.ts by the following commands:

npm install @openapi-codegen/cli
npm install @openapi-codegen/typescript
npm install --only=dev
npx openapi-codegen gen api

Tests

We use tox to run our tests, which should be installed into the python venv from our requirements.txt.

Invoke the tests using tox -e test and tox -e lint to run the linter.

Production Setup

Create a .env.production file in the repo root with the following variables. Access supports running against PostgreSQL 14 and above.

OKTA_DOMAIN=<YOUR_OKTA_DOMAIN> # For example, "mydomain.okta.com"
OKTA_API_TOKEN=<YOUR_OKTA_API_TOKEN>
DATABASE_URI=<YOUR_DATABASE_URI> # For example, "postgresql+pg8000://postgres:postgres@localhost:5432/access"
CLIENT_ORIGIN_URL=http://localhost:3000
REACT_APP_API_SERVER_URL=""
FLASK_SENTRY_DSN=https://<key>@sentry.io/<project>
REACT_SENTRY_DSN=https://<key>@sentry.io/<project>

Google Cloud CloudSQL Configuration

If you want to use the CloudSQL Python Connector, set the following variables in your .env.production file:

CLOUDSQL_CONNECTION_NAME=<YOUR_CLOUDSQL_CONNECTION_NAME> # For example, "project:region:instance-name"
DATABASE_URI="postgresql+pg8000://"
DATABASE_USER=<YOUR_DATABASE_USER> # For a service account, this is the service account's email without the .gserviceaccount.com domain suffix.
DATABASE_NAME=<YOUR_DATABASE_NAME>
DATABASE_USES_PUBLIC_IP=[True|False]

Authentication

Authentication is required when running Access in production. Currently, we support OpenID Connect (OIDC) (including Okta) and Cloudflare Access as methods to authenticate users to Access.

OpenID Connect (OIDC)

To use OpenID Connect (OIDC) authentication, such as with Okta:

Go to your Okta Admin dashboard -> Applications -> Create App Integration.

In the Create a new app integration, select:

  • Sign-in method: OIDC - OpenID Connect
  • Application type: Web Application

Then on the New Web App Integration page:

  • App integration name: Access
  • Logo: (optional)
  • Grant type:
    • Client acting on behalf of user: Authorization Code
  • Sign-in redirect URIs: https://<YOUR_ACCESS_DEPLOYMENT_DOMAIN_NAME>/oidc/authorize
  • Sign-out redirect URIs: https://<YOUR_ACCESS_DEPLOYMENT_DOMAIN_NAME>/oidc/logout

Then click Save and go to the General tab of the new app integration to find the Client ID and Client secret. You'll need these for the next step.

Create a client_secrets.json file containing your OIDC client secrets, that looks something like the following:

{
  "secrets": {
    "client_id":"<YOUR_OKTA_APPLICATION_CLIENT_ID>",
    "client_secret":"<YOUR_OKTA_APPLICATION_CLIENT_SECRET>",
    "issuer": "https://<YOUR_OKTA_INSTANCE>.okta.com/"
  }
}

Then set the following variables in your .env.production file:

# Generate a good secret key using `python -c 'import secrets; print(secrets.token_hex())'`
# this is used to encrypt Flask cookies
SECRET_KEY=<YOUR_SECRET_KEY>
# The path to your client_secrets.json file or if you prefer, inline the entire JSON string
OIDC_CLIENT_SECRETS=./client_secrets.json or '{"secrets":..'

Cloudflare Access

To use Cloudflare Access authentication, set up a Self-Hosted Cloudflare Access Application using a Cloudflare Tunnel. Next, set the following variables in your .env.production file:

# Your Cloudflare "Team domain" under Zero Trust -> Settings -> Custom Pages in the Cloudflare dashboard
# For example, "mydomain.cloudflareaccess.com"
CLOUDFLARE_TEAM_DOMAIN=<CLOUDFLARE_ACCESS_TEAM_DOMAIN>
# Your Cloudflare "Audience" tag under Zero Trust -> Access -> Applications -> <Your Application> -> Overview in the Cloudflare dashboard
# found under "Application Audience (AUD) Tag"
CLOUDFLARE_APPLICATION_AUDIENCE=<CLOUFLARE_ACCESS_AUDIENCE_TAG>

Docker Build and Run

Build the Docker image:

docker build -t access .

Or build and run it using Docker Compose:

docker compose up --build

The command above will build and run the container.

Go to http://localhost:3000/ to view the application.

Docker configuration

Before launching the container with Docker, make sure to configure .env.psql and .env.production:

Configuration for .env.psql

The .env.psql file is where you configure the PostgreSQL server credentials, which is also Dockerized.

  • POSTGRES_USER: Specifies the username for the PostgreSQL server.
  • POSTGRES_PASSWORD: Specifies the password for the PostgreSQL server.

Configuration for .env.production

The .env.production file is where you configure the application.

  • OKTA_DOMAIN: Specifies the Okta domain to use.
  • OKTA_API_TOKEN: Specifies the Okta API Token to use.
  • DATABASE_URI: Specifies the Database connection URI. Example: postgresql+pg8000://<POSTGRES_USER>:<POSTGRES_PASSWORD>@postgres:5432/<DB_NAME>.
  • CLIENT_ORIGIN_URL: Specifies the origin URL which is used by CORS.
  • REACT_APP_API_SERVER_URL: Specifies the API base URL which is used by the frontend. Set to an empty string "" to use the same URL as the frontend.
  • FLASK_SENTRY_DSN: See the Sentry documentation. [OPTIONAL] You can safely remove this from your env file
  • REACT_SENTRY_DSN: See the Sentry documentation. [OPTIONAL] You can safely remove this from your env file
  • CLOUDFLARE_TEAM_DOMAIN: Specifies the Team Domain used by Cloudflare Access.
  • CLOUDFLARE_APPLICATION_AUDIENCE: Specifies the Audience Tag used by Cloudflare Access.
  • SECRET_KEY: Specifies the secret key used to encrypt flask cookies. WARNING: Ensure this is something secure you can generate a good secret key using python -c 'import secrets; print(secrets.token_hex())'.
  • OIDC_CLIENT_SECRETS: Specifies the path to your client_secrets.json file or if you prefer, inline the entire JSON string.

Check out .env.psql.example or .env.production.example for an example configuration file structure.

NOTE:

If you are using Cloudflare Access, ensure that you configure CLOUDFLARE_TEAM_DOMAIN and CLOUDFLARE_APPLICATION_AUDIENCE. SECRET_KEY and OIDC_CLIENT_SECRETS do not need to be set and can be removed from your env file.

Else, if you are using a generic OIDC identity provider (such as Okta), then you should configure SECRET_KEY and OIDC_CLIENT_SECRETS. CLOUDFLARE_TEAM_DOMAIN and CLOUDFLARE_APPLICATION_AUDIENCE do not need to be set and can be removed from your env file. Make sure to also mount your client-secrets.json file to the container if you don't have it inline.

Database Setup

After docker-compose up --build, you can run the following commands to setup the database:

Create the database in the postgres container:

docker compose exec postgres createdb -U <POSTGRES_USER> <DB_NAME>

Run the initial migrations and seed the initial data from Okta:

docker compose exec discord-access /bin/bash

Then run the following commands inside the container:

flask db upgrade
flask init <YOUR_OKTA_USER_EMAIL>

Visit http://localhost:3000/ to view your running version of Access!

Kubernetes Deployment and CronJobs

As Access is a web application packaged with Docker, it can easily be deployed to a Kubernetes cluster. We've included example Kubernetes yaml objects you can use to deploy Access in the examples/kubernetes directory.

These examples include a Deployment, Service, Namespace, and Service Account object for serving the stateless web application. Additionally there are examples for deploying the flask sync and flask notify commands as cronjobs to periodically synchronize users, groups, and their memberships and send expiring access notifications respectively.

Plugins

Access uses the Python pluggy framework to allow for new functionality to be added to the system. Plugins are Python packages that are installed into the Access Docker container. For example, a notification plugin could add a new type of notification such as Email, SMS, or a Discord message for when new access requests are made and resolved.

Creating a Plugin

Plugins in Access follow the conventions defined by the Python pluggy framework.

An example implementation of a notification plugin is included in examples/plugins/notifications, which can be extended to send messages using custom Python code. It implements the NotificationPluginSpec found in notifications.py

There's also an example implementation of a conditional access plugin in examples/plugins/conditional_access, which can be extended to conditionally approve or deny requests. It implements the ConditionalAccessPluginSpec found in requests.py.

Installing a Plugin in the Docker Container

Below is an example Dockerfile that would install the example notification plugin into the Access Docker container, which was built above using the top-level application Dockerfile. The plugin is installed into the /app/plugins directory and then installed using pip.

FROM access:latest

WORKDIR /app/plugins
ADD ./examples/plugins/ ./

RUN pip install ./notifications

WORKDIR /app

TODO

Here are some of the features we're potentially planning to add to Access:

  • A Group Lifecycle and User Lifecycle plugin framework
  • Support for Google Groups and Github Teams via Group Lifecycle plugins
  • Group (and Role) creation requests
  • Role membership requests, so Role owners can request to add their Role to a Group
  • OktaApp model with many-to-many relationship to App for automatically assigning AppGroups to Okta application tiles
  • A webhook to synchronize group memberships and disabling users in real-time from Okta

License

Copyright (C) 2024 Discord Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For code dependencies, libraries, and frameworks used by this project that are dual-licensed or allow the option under their terms to select either the Apache Version 2.0 License, MIT License, or BSD 3-Clause License, this project selects those licenses for use of those dependencies in that order of preference.

access's People

Contributors

3ur avatar androothechen avatar dependabot[bot] avatar eguerrant avatar goakley avatar hackermondev avatar somethingnew2-0 avatar ternera avatar v-zer0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

access's Issues

retry on resp.next()

more converstaions with @somethingnew2-0

i noticed this "pattern" 4 times (paginating thru a given api)

users, resp, error = await OktaService._retry(self.okta_client.list_users)
if error is not None:
raise Exception(error)
assert users is not None and resp is not None
while resp.has_next():
more_users, _ = await resp.next()
users.extend(more_users)
return list(map(lambda user: User(user), users))

don't u need to retry the next(), too?

i use async generators for it. write it once, factor it out, reuse it everywhere. something like

import asyncio
from okta.client import Client as OktaClient

async def get_applications():
    async with OktaClient() as okta_client:
        filter = {'filter': 'status eq "ACTIVE"'}
        async for app in get_objects(okta_client.list_applications(filter)):
            print(f"Application Name: {app.name}")
            print(f"Application ID: {app.id}")
            async for app_user in get_objects(okta_client.list_application_users(app.id)):
                print(app_user.id)
            print()

async def get_objects(coro):
    objects, resp, _ = await coro
    while objects:
        for object in objects:
            yield object
        objects, _ = await resp.next() if resp.has_next() else (None, None)

asyncio.run(get_applications())

but that might not work here. i wrote it a while ago. maybe i should rewrite it

use Okta's response headers for rate limits

transcript of (Gabriel's half of) a conversation with @somethingnew2-0

why do u use exponential backoff when Okta tells you when u can retry the api call?

async def _retry(func: Callable[[Any], Any], *args: Any, **kwargs: Any) -> Any:
"""Retry Okta API requests with specific status codes using exponential backoff."""
for attempt in range(1 + REQUEST_MAX_RETRIES):
result = await func(*args, **kwargs)
if len(result) == 2:
response, error = result
elif len(result) == 3:
_, response, error = result
else:
raise Exception("Unexpected result structure from Okta client.")
if (attempt == REQUEST_MAX_RETRIES or
error is None or
response is None or
(response is not None and response.get_status() not in RETRIABLE_STATUS_CODES)):
return result
if response is None:
logger.warning('Got None response from Okta resource. Retrying...')
else:
logger.warning(f'Got {response.get_status()} response from Okta resource {response._url}, with error:'
f' {error}. Retrying...'
)
await asyncio.sleep(RETRY_BACKOFF_FACTOR * (2**attempt))

if u get a 429 error that tells u when to retry, why not look at those headers?
eg (and this isn't perfect, but...)
https://github.com/gabrielsroka/gabrielsroka.github.io/blob/master/console/index.html#L169-L187

ie, if u reach the rate limit at 10:00:00 and it tells u to retry at 10:01:00, there's no point in retrying at 10:00:01.2, 10:00:02.4, 10:00:04.8. ur just gonna get more errors

Okta provides three headers in each response to report on both concurrent and org-wide rate limits.
For org-wide rate limits, the three headers show the limit that is being enforced, when it resets, and how close you are to hitting the limit:
X-Rate-Limit-Limit - the rate limit ceiling that is applicable for the current request.
X-Rate-Limit-Remaining - the number of requests left for the current rate-limit window.
X-Rate-Limit-Reset - the time at which the rate limit resets, specified in UTC epoch time (in seconds).

https://developer.okta.com/docs/reference/rl-best-practices/#check-your-rate-limits-with-okta-s-rate-limit-headers

On an unmodified new clone of Access, non-Okta Admin users are unable to cancel their own Pending Access requests

Hello! I just came back from a short break and am poking around at Discord Access again. I'm not sure when, but there seems to be some sort of regression (at least on my local) where non-Okta Admin users are unable to cancel their own Pending Access Requests:

  • What I see in the UI
    image

  • What I see running in local dev mode in the Terminal
    image

  • It's running under Python 3.12.3 on my Intel Mac in a VENV (just following the setup on the README).

What I just did 5 minutes ago:

  1. gh repo clone discord/access access-unchanged
  2. copy my .env setup from my other dev fork over to this new one. I had an Okta Admin user in the .env for CURRENT_OKTA_USER_EMAIL
  3. Follow the README as is
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
flask db upgrade
flask init <YOUR_OKTA_USER_EMAIL>    #where I init with and Okta Admin User
export CURRENT_OKTA_USER_EMAIL="[email protected]". #i switch over with an ENV VAR over to a Okta NonAdmin User
  1. Create a request under the nonAdmin and then run into the issue trying to cancel it but bump into the error.
  2. Exit the app, export CURRENT_OKTA_USER_EMAIL to an Okta Admin user, flask run. After this, I'm able to create new access requests to the same group but also cancel them (when pending).

I'm pretty sure I tested non-Okta Admin users being able to cancel their own requests before - is this a regression?

Help with SQLAlchemy error

Hi!

I've got a kubernetes deployment of access and I'm getting a blank page when trying to access the UI. Digging into the logs, below is what I'm getting. Can you assist with this?

Thanks!

kubectl -n dev-access logs -f -l app=dev-access
WHERE okta_user.email ILIKE %(email_1)s AND okta_user.deleted_at IS NULL
 LIMIT %(param_1)s
2024-05-17 19:53:15,123 INFO sqlalchemy.engine.Engine [cached since 264.8s ago] {'email_1': '[email protected]', 'param_1': 1}
[cached since 264.8s ago] {'email_1': '[email protected]', 'param_1': 1}
2024-05-17 19:53:15,125 INFO sqlalchemy.engine.Engine ROLLBACK
ROLLBACK
[17/May/2024:19:53:15 +0000] "GET /manifest.json HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15"
2024-05-17 19:53:15,127 INFO sqlalchemy.engine.Engine ROLLBACK
ROLLBACK
[17/May/2024:19:53:15 +0000] "GET /static/js/main.764bef9b.js HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15"

Argument 'postgresql_where' is not accepted by dialect 'postgresql' on behalf of <class 'sqlalchemy.sql.schema.UniqueConstraint'>

πŸ‘‹πŸ» Hey guys, I saw your new blog and thought I should check it out.
πŸ”Ž Upon setting up, while running the flask db upgrade command to seed the DB, it turns out that it throws up an error.
⚠️ Error:

Traceback (most recent call last):
  File "/home/scott/access/venv/bin/flask", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/flask/cli.py", line 1107, in main
    cli.main()
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/flask/cli.py", line 388, in decorator
    return ctx.invoke(f, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/flask_migrate/cli.py", line 154, in upgrade
    _upgrade(directory, revision, sql, tag, x_arg)
  File "/home/scott/access/venv/lib/python3.11/site-packages/flask_migrate/__init__.py", line 111, in wrapped
    f(*args, **kwargs)
  File "/home/scott/access/venv/lib/python3.11/site-packages/flask_migrate/__init__.py", line 200, in upgrade
    command.upgrade(config, revision, sql=sql, tag=tag)
  File "/home/scott/access/venv/lib/python3.11/site-packages/alembic/command.py", line 403, in upgrade
    script.run_env()
  File "/home/scott/access/venv/lib/python3.11/site-packages/alembic/script/base.py", line 583, in run_env
    util.load_python_file(self.dir, "env.py")
  File "/home/scott/access/venv/lib/python3.11/site-packages/alembic/util/pyfiles.py", line 95, in load_python_file
    module = load_module_py(module_id, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/alembic/util/pyfiles.py", line 113, in load_module_py
    spec.loader.exec_module(module)  # type: ignore
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/scott/access/migrations/env.py", line 92, in <module>
    run_migrations_online()
  File "/home/scott/access/migrations/env.py", line 86, in run_migrations_online
    context.run_migrations()
  File "<string>", line 8, in run_migrations
  File "/home/scott/access/venv/lib/python3.11/site-packages/alembic/runtime/environment.py", line 948, in run_migrations
    self.get_context().run_migrations(**kw)
  File "/home/scott/access/venv/lib/python3.11/site-packages/alembic/runtime/migration.py", line 627, in run_migrations
    step.migration_fn(**kw)
  File "/home/scott/access/migrations/versions/d6db40b0805d_initial_migration.py", line 79, in upgrade
    sa.UniqueConstraint("email", name=op.f('idx_email'), postgresql_where=sa.text("deleted_at IS NULL")),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/scott/access/venv/lib/python3.11/site-packages/sqlalchemy/sql/schema.py", line 4340, in __init__
    Constraint.__init__(
  File "/home/scott/access/venv/lib/python3.11/site-packages/sqlalchemy/sql/schema.py", line 4117, in __init__
    self._validate_dialect_kwargs(dialect_kw)
  File "/home/scott/access/venv/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 618, in _validate_dialect_kwargs
    raise exc.ArgumentError(
sqlalchemy.exc.ArgumentError: Argument 'postgresql_where' is not accepted by dialect 'postgresql' on behalf of <class 'sqlalchemy.sql.schema.UniqueConstraint'>

πŸ’‘ Keep in mind that I'm not very familiar with Python and databases. So I might be dumb at some times.

request to expose Group Active tags to Conditional Access Plugin

I'd like to make some conditional access decisions based on the tags that are applied to an Okta Group within Access.

I have an exploratory branch here if it's useful.

    # Auto Approve if "AutoApproval" is applied to Group
    if not access_request.request_ownership and "AutoApproval" in group_tags:
        logger.info(f"Auto-approving access request {access_request.id} to group {group.name}")
        return ConditionalAccessResponse(
            approved=True,
            reason="Group membership auto-approved based on AutoApproval tag",
            ending_at=access_request.request_ending_at,
        )

Please help troubleshoot issue of user not removed from group in Okta on expire

Hello!

can you please help troubleshoot issue of user not removed from group on expire?

  1. If i add user to group in Access Portal, the user is added in Okta;
  2. If user is remove from group in Okta, syncer removes it in Access Portal.

However if i add user to group in Access Portal with expire

127.0.0.6 - - [23/Aug/2024:10:48:40 +0000] "PUT /api/groups/--redacted-id---/members HTTP/1.1" 200 117 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"

..the user is removed in Access portal after expire, but not in Okta.
After syncer run, the user is synced back from Okta to Access Portal (re-added back).

In the portal log i see no request to DELETE / etc. group resource, but GET to site pages and POST /api/bugs/sentry HTTP/1.1.

Would very much appreciate if you help troubleshoot the issue πŸ™‡β€β™‚οΈ

can't compare offset-naive and offset-aware datetimes in tag.py

Issue with datetime object comparisons related to timezone awareness and comparison. Report on CloudSec Slack forum as well.

Issue appears when testing the Conditional Access plugin and adding any Group tag to the "Auto-Approved-Group".

 File "/Users/jonathan.le/dev/jonathanhle/access/api/operations/approve_access_request.py", line 122, in execute
    ModifyGroupUsers(
  File "/Users/jonathan.le/dev/jonathanhle/access/api/operations/modify_group_users.py", line 56, in __init__
    self.members_added_ended_at = coalesce_ended_at(
                                  ^^^^^^^^^^^^^^^^^^
  File "/Users/jonathan.le/dev/jonathanhle/access/api/models/tag.py", line 43, in coalesce_ended_at
    return min(constraint_ended_at, initial_ended_at)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: can't compare offset-naive and offset-aware datetimes

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.