Exemple #1
0
def logout(request, *args, **argv):  #pylint: disable=unused-argument
    """Functional view to remove a user session.
    FIXME: Does not appear to delete the user's session!
    """
    try:
        print('Signing user out.')
        session_cookie = request.COOKIES.get('__session')
        if session_cookie is None:
            session_cookie = request.session['__session']
        claims = verify_session_cookie(session_cookie)
        uid = claims['uid']
        create_log(ref=f'users/{uid}/logs',
                   claims=claims,
                   action='Signed out.',
                   log_type='auth',
                   key='logout')
        update_document(f'users/{uid}', {'signed_in': False})
        print('Updated user as signed-out in Firestore:', uid)
        revoke_refresh_tokens(claims['sub'])
        # request.session['__session'] = ''
        response = HttpResponse(status=205)
        # response = JsonResponse({'success': True}, status=205)
        response['Set-Cookie'] = '__session=None; Path=/'
        response['Cache-Control'] = 'public, max-age=300, s-maxage=900'
        return response
    except:
        # request.session['__session'] = ''
        response = HttpResponse(status=205)
        # response = JsonResponse({'success': True}, status=205)
        response['Set-Cookie'] = '__session=None; Path=/'
        response['Cache-Control'] = 'public, max-age=300, s-maxage=900'
        return response
Exemple #2
0
def delete_api_key(request, *args, **argv):  #pylint: disable=unused-argument
    """Deletes a user's API key passed through an authorization header,
    e.g. `Authorization: API-key xyz`.
    Args:
        request (HTTPRequest): A request to get the user's API key.
    """
    user_claims = verify_session(request)
    uid = user_claims['uid']
    post_data = loads(request.body.decode('utf-8'))

    # FIXME: Get the name of the desired key to delete.

    # Delete the key from the users API keys.

    # Remove the key HMAC by created_at time.

    # authorization = request.META['HTTP_AUTHORIZATION']
    # api_key = authorization.split(' ')[-1]
    # app_secret = get_document('admin/api')['app_secret_key']
    # code = sha256_hmac(app_secret, api_key)
    # key_data = get_document(f'admin/api/api_key_hmacs/{code}')
    # uid = key_data['uid']
    # delete_document(f'admin/api/api_key_hmacs/{code}')
    # delete_document(f'users/{uid}/api_key_hmacs/{code}')
    # return JsonResponse({'status': 'success'})
    create_log(f'users/{uid}/logs', user_claims, 'Deleted API key.', 'api_key',
               'api_key_delete', [{
                   'deleted_at': datetime.now().isoformat()
               }])
    message = 'Delete API key not yet implemented, will be implemented shortly.'
    return JsonResponse({'error': True, 'message': message})
Exemple #3
0
def users(request):
    """Get, update, or create user's data."""
    print('Request to users endpoint!')
    try:

        # Authenticate the user.
        claims = authenticate_request(request)

        # Get user data.
        if request.method == 'GET':
            user_data = get_document(f'users/{claims["uid"]}')
            return Response(user_data, content_type='application/json')

        # Edit user data.
        if request.method == 'POST':

            # Get the user's ID.
            post_data = loads(request.body.decode('utf-8'))
            uid = claims['uid']
            post_data['uid'] = uid

            # Update the user's data, create a log, and return the data.
            try:
                update_document(f'users/{uid}', post_data)
                create_log(ref=f'users/{uid}/logs',
                           claims=claims,
                           action='Updated user data.',
                           log_type='users',
                           key='user_data',
                           changes=[post_data])
                return Response(post_data, content_type='application/json')

            except:

                # Create the user's data, create a log, and return the data.
                user_email = post_data['email']
                user = {
                    'email': user_email,
                    'created_at': utils.get_timestamp(),
                    'uid': post_data['uid'],
                    'photo_url':
                    f'https://robohash.org/${user_email}?set=set5',
                }
                update_document(f'users/{uid}', post_data)
                create_log(f'users/{uid}/logs', claims, 'Created new user.',
                           'users', 'user_data', [post_data])
                return Response(user, content_type='application/json')

    except:

        # Return a server error.
        return Response({'success': False},
                        content_type='application/json',
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Exemple #4
0
def login(request, *args, **argv):  #pylint: disable=unused-argument
    """Functional view to create a user session.
    Optional: Ensure that the request succeeds on the client!
    """
    try:
        print('Logging user in...')
        authorization = request.headers.get('Authorization', '')
        token = authorization.split(' ').pop()
        if not token:
            # return HttpResponse(status=401)
            message = 'Authorization token not provided in the request header.'
            return JsonResponse({
                'error': True,
                'message': message
            },
                                status=401)
        initialize_firebase()
        print('Initialized Firebase.')

        # Set session cookie in a cookie in the response.
        # response = HttpResponse(status=200)
        response = JsonResponse({'success': True}, status=200)
        # Optional: Let user specify cookie duration?
        # expires_in = timedelta(days=5)
        # expires = datetime.now() + expires_in
        session_cookie = create_session_cookie(token)
        response['Set-Cookie'] = f'__session={session_cookie}; Path=/'
        response[
            'Cache-Control'] = 'public, max-age=300, s-maxage=900'  # TODO: Set the expiration time

        # Save session cookie in the session.
        # Preferred over cookies (but cookies are still needed for production).
        request.session['__session'] = session_cookie

        # Verify the user, create a log, update the user as signed-in,
        # and return a response with the session cookie.
        claims = verify_token(token)
        uid = claims['uid']
        print('Verified user with Firebase Authentication:', claims['email'])
        create_log(ref=f'users/{uid}/logs',
                   claims=claims,
                   action='Signed in.',
                   log_type='auth',
                   key='login')
        update_document(f'users/{uid}', {'signed_in': True})
        print('Logged user sign-in in Firestore:', uid)
        return response
    except:
        # return HttpResponse(status=401)
        message = 'Authorization failed in entirety. Please contact support.'
        return JsonResponse({'error': True, 'message': message}, status=401)
def download_dataset(claims, collection, data_points):
    """Download a given dataset."""

    # Get the user's data, returning if not authenticated.
    try:
        uid = claims['uid']
        user_email = claims['email']
        name = claims.get('name', 'Unknown')
    except KeyError:
        return None, None

    # Get data points in specified order.
    collection_data = get_collection(collection, order_by='state')
    dataframe = DataFrame.from_dict(collection_data, orient='columns')
    data = dataframe[data_points]

    # Convert JSON to CSV.
    with NamedTemporaryFile(delete=False) as temp:
        temp_name = temp.name + '.csv'
        data.to_csv(temp_name, index=False)
        temp.close()

    # Post a copy of the data to Firebase storage.
    now = datetime.now()
    timestamp = now.strftime('%Y-%m-%d_%H-%M-%S')
    destination = 'public/data/downloads/'
    data_type = collection.split('/')[-1]
    filename = f'{data_type}_{timestamp}.csv'
    ref = destination + filename
    upload_file(ref, temp_name, bucket_name=STORAGE_BUCKET)

    # Create an activity log.
    log_entry = {
        'data_points': len(data),
        'file': ref,
        'email': user_email,
        'name': name,
        'uid': uid,
    }
    create_log(
        ref='logs/website/downloads',
        claims=claims,
        action=f'User ({user_email}) downloaded {data_type} data.',
        log_type='download',
        key=f'download_{data_type}_data',
        changes=log_entry,
    )

    # Return the file that can be downloaded.
    return temp_name, filename
def delete_user_pin(request, *args, **argv): #pylint: disable=unused-argument
    """Delete all pins for a given user, removing the data stored with their hash.
    Args:
        request (HTTPRequest): A request to get the user's session.
    Returns:
        (JsonResponse): A JSON response containing the API key in an
            `api_key` field.
    """
    user_claims = authenticate_request(request)
    uid = user_claims['uid']
    query = {'key': 'uid', 'operation': '==', 'value': uid}
    existing_pins = get_collection('admin/api/pin_hmacs', filters=[query])
    for pin in existing_pins:
        code = pin['hmac']
        delete_document(f'admin/api/pin_hmacs/{code}')
    delete_field(f'users/{uid}', 'pin_created_at')
    create_log(f'users/{uid}/logs', user_claims, 'Deleted pin.', 'pin', 'pin_delete', [{'deleted_at': datetime.now().isoformat()}])
    return JsonResponse({'success': True, 'message': 'User pin deleted.'})
def test_create_log():
    """Test create a log."""
    
    # Initialize Firebase.
    env = environ.Env()
    env.read_env('../.env')
    credentials = env('GOOGLE_APPLICATION_CREDENTIALS')
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials
    db = firebase.initialize_firebase()

    # Create a test log.
    user = {
        'uid': 'test',
        'display_name': 'CannBot',
        'email': '*****@*****.**',
        'photo_url': 'https://robohash.org/[email protected]',
    }
    logs = 'tests/test_collections/logs'
    firebase.create_log(logs, user, 'Test log.', 'test', 'create_log')
def delete_signature(request, *args, **argv): #pylint: disable=unused-argument
    """Delete a user's signature.
    Args:
        request (HTTPRequest): A request to get the user's session.
    Returns:
        (JsonResponse): A JSON response containing the user's claims.
    """
    user_claims = authenticate_request(request)
    uid = user_claims['uid']
    entry = {
        'signature_created_at': '',
        'signature_url': '',
        'signature_ref': '',
    }
    delete_file(BUCKET_NAME, f'users/{uid}/user_settings/signature.png')
    update_document(f'users/{uid}', entry)
    update_document(f'users/{uid}/user_settings/signature', entry)
    create_log(f'users/{uid}/logs', user_claims, 'Deleted signature.', 'signature', 'signature_delete', [{'deleted_at': datetime.now().isoformat()}])
    return JsonResponse({'success': True, 'message': 'Signature deleted.'})
Exemple #9
0
def update_object(request, claims, model_type, model_type_singular,
                  organization_id):
    """Create or update object(s) through the API.
    Parse the data and add the data to Firestore.
    Return the data and success.
    Args:
        request (HTTPRequest): An HTTP request used to retrieve parameters.
        claims (dict): User-specific custom claims.
        model_type (str): The type of data model.
        model_type_singular (str): The singular of the type of data model.
        organization_id (str): An organization ID to narrow matches.

    Returns:
        (list): A list of dictionaries of the data posted.
    """
    updated_at = datetime.now().isoformat()
    data = loads(request.body.decode('utf-8'))
    if isinstance(data, dict):
        doc_id = data[f'{model_type_singular}_id']
        data['updated_at'] = updated_at
        data['updated_by'] = claims['uid']
        update_document(
            f'organizations/{organization_id}/{model_type}/{doc_id}', data)
    elif isinstance(data, list):
        for item in data:
            doc_id = item[f'{model_type_singular}_id']
            item['updated_at'] = updated_at
            item['updated_by'] = claims['uid']
            print('Saving item:\n', item)
            update_document(
                f'organizations/{organization_id}/{model_type}/{doc_id}', item)
    else:
        return []
    update_totals(model_type, organization_id, doc_id)
    if model_type != 'logs':
        changes = [data]
        if isinstance(data, list):
            changes = data
        create_log(f'organizations/{organization_id}/logs', claims,
                   f'{model_type.title()} edited.', model_type, doc_id,
                   changes)
    return data
Exemple #10
0
def delete_license(request, *args, **argv): #pylint: disable=unused-argument
    """Delete a license from an organization's licenses."""

    # Authenticate the user.
    _, project_id = google.auth.default()
    user_claims = authenticate_request(request)
    data = loads(request.body.decode('utf-8'))
    deletion_reason = data.get('deletion_reason', 'No deletion reason.')
    license_number = request.query_params.get('license')
    org_id = request.query_params.get('license')
    if not license_number or not org_id:
        message = 'Parameters `license` and `org_id` are required.'
        return Response({'error': True, 'message': message}, status=403)

    # Delete the license data and redact the secret.
    doc = get_document(f'organizations/{org_id}')
    existing_licenses = doc['licenses']
    licenses = []
    for license_data in existing_licenses:
        license_number = license_data['license_number']
        if license_data['license_number'] != license_number:
            licenses.append(license_data)
        else:
            add_secret_version(
                project_id,
                license_data['user_api_key_secret']['secret_id'],
                'redacted'
            )
    doc['licenses'] = licenses
    update_document(f'organizations/{org_id}', doc)

    # Create a log.
    create_log(
        ref=f'organizations/{org_id}/logs',
        claims=user_claims,
        action='License deleted.',
        log_type='traceability',
        key='delete_license',
        changes=[license_number, deletion_reason]
    )
    return JsonResponse({'status': 'success', 'message': 'License deleted.'})
Exemple #11
0
def delete_object(request, claims, model_id, model_type, model_type_singular,
                  organization_id):
    """Delete an object through the API.
    Parse the data. Check that the user is an owner or quality assurance.
    Delete by URL ID if given. Otherwise delete by using posted ID(s).
    """
    qa = claims.get('qa', '')
    owner = claims.get('owner', '')
    data = loads(request.body.decode('utf-8'))
    if organization_id not in qa and organization_id not in owner:
        return False
    if model_id:
        delete_document(
            f'organizations/{organization_id}/{model_type}/{model_id}')
        create_log(f'organizations/{organization_id}/logs', claims,
                   f'{model_type.title()} deleted.', model_type, model_id,
                   [data])
    else:
        if isinstance(data, dict):
            doc_id = data[f'{model_type_singular}_id']
            delete_document(
                f'organizations/{organization_id}/{model_type}/{doc_id}')
            create_log(f'organizations/{organization_id}/logs', claims,
                       f'{model_type.title()} deleted.', model_type, doc_id,
                       [data])
        elif isinstance(data, list):
            for item in data:
                doc_id = item[f'{model_type_singular}_id']
                delete_document(
                    f'organizations/{organization_id}/{model_type}/{doc_id}')
                create_log(f'organizations/{organization_id}/logs', claims,
                           f'{model_type.title()} deleted.', model_type,
                           doc_id, [data])
    return True
Exemple #12
0
def create_api_key(request, *args, **argv):  #pylint: disable=unused-argument
    """Mint an API key for a user, granting programmatic use at the same
    level of permission as the user.
    Args:
        request (HTTPRequest): A request to get the user's session.
    Returns:
        (JsonResponse): A JSON response containing the API key in an
            `api_key` field.
    """
    user_claims = verify_session(request)
    uid = user_claims['uid']
    api_key = token_urlsafe(48)
    app_secret = get_document('admin/api')['app_secret_key']
    code = sha256_hmac(app_secret, api_key)
    post_data = loads(request.body.decode('utf-8'))
    now = datetime.now()
    expiration_at = post_data['expiration_at']
    try:
        expiration_at = datetime.fromisoformat(expiration_at)
    except:
        expiration_at = datetime.strptime(expiration_at, '%m/%d/%Y')
    if expiration_at - now > timedelta(365):
        expiration_at = now + timedelta(365)
    key_data = {
        'created_at': now.isoformat(),
        'expiration_at': expiration_at.isoformat(),
        'name': post_data['name'],
        'permissions': post_data['permissions'],
        'uid': uid,
        'user_email': user_claims['email'],
        'user_name': user_claims.get('name', 'No Name'),
    }
    update_document(f'admin/api/api_key_hmacs/{code}', key_data)
    update_document(f'users/{uid}/api_key_hmacs/{code}', key_data)
    create_log(f'users/{uid}/logs', user_claims, 'Created API key.', 'api_key',
               'api_key_create', [key_data])
    return JsonResponse({'status': 'success', 'api_key': api_key})
Exemple #13
0
def organizations(request, organization_id=None, type='lab'):
    """Get, create, or update organizations.
    E.g.
        ```
        organization = {
                'owner': [],
                'name': '',
                'license': '',
                'type': '',
                'team': [],
                'support': '',
            }
        ```
    """

    # Get endpoint variables.
    model_type = 'organizations'
    _, project_id = google.auth.default()
    claims = authenticate_request(request)
    uid = claims['uid']
    print('User request to organizations:', uid)

    # Get organization(s).
    if request.method == 'GET':

        # Get organization_id parameter
        if organization_id:
            print('Query organizations by ID:', organization_id)
            data = get_document(f'{model_type}/{organization_id}')
            print('Found data:', data)
            if not data:
                message = 'No organization exists with the given ID.'
                return Response({
                    'error': True,
                    'message': message
                },
                                status=404)
            elif data['public']:
                return Response({'data': data}, status=200)
            elif uid not in data['team']:
                message = 'This is a private organization and you are not a team member. Request to join before continuing.'
                return Response({
                    'error': True,
                    'message': message
                },
                                status=400)
            else:
                return Response({'data': data}, status=200)

        # TODO: Get query parameters.
        keyword = request.query_params.get('name')
        if keyword:
            print('Query by name:', keyword)
            query = {'key': 'name', 'operation': '==', 'value': keyword}
            docs = get_collection(model_type, filters=[query])
            return Response({'data': docs}, status=200)

        # Get all of a user's organizations
        else:
            query = {
                'key': 'team',
                'operation': 'array_contains',
                'value': uid
            }
            docs = get_collection(model_type, filters=[query])
            return Response({'data': docs}, status=200)

        # Optional: Get list of other organizations.
        # Check if user is in organization's team, otherwise,
        # only return publically available information.

        # Optional: Try to get facility data from Metrc.
        # facilities = track.get_facilities()

    # Create or update an organization.
    elif request.method == 'POST':

        # Update an organization with the posted data if there is an ID.
        data = loads(request.body.decode('utf-8'))
        if organization_id:

            # Return an error if the organization already exists
            # and the user is not part of the organization's team.
            doc = get_document(f'{model_type}/{organization_id}')
            if not doc:
                message = 'No data exists for the given ID.'
                return Response({
                    'error': True,
                    'message': message
                },
                                status=400)

            organization_id = doc['uid']
            team_list = claims.get('team', [])
            owner_list = claims.get('owner', [])
            if uid not in team_list and organization_id not in owner_list:
                message = 'You do not currently belong to this organization. Request to join before continuing.'
                return Response({
                    'error': True,
                    'message': message
                },
                                status=400)

            # If an organization already exists, then only the owner
            # can edit the organization's team.
            if uid != doc['owner']:
                data['team'] = doc['team']

            # Store posted API keys as secrets.
            # FIXME: Update licenses if they are being edited.
            new_licenses = data.get('licenses')
            if new_licenses:
                licenses = doc.get('licenses', [])
                for license_data in new_licenses:
                    license_number = license_data['license_number']
                    secret_id = f'{license_number}_secret'
                    try:
                        create_secret(project_id, secret_id,
                                      license_data['user_api_key'])
                    except:
                        pass
                    secret = add_secret_version(project_id, secret_id,
                                                license_data['user_api_key'])
                    version_id = secret.split('/')[-1]
                    license_data['user_api_key_secret'] = {
                        'project_id': project_id,
                        'secret_id': secret_id,
                        'version_id': version_id,
                    }
                    del license_data['user_api_key']
                    licenses.append(license_data)
                doc['licenses'] = licenses

        # Create organization if it doesn't exist
        # All organizations have a unique `organization_id`.
        else:
            doc = {}
            organization_id = slugify(data['name'])
            doc['organization_id'] = organization_id
            doc['team'] = [uid]
            doc['owner'] = uid

            # Identify created organization type.
            doc['type'] = type

            # All organizations start with the standard data models.
            # FIXME: Remove data models that have permissions
            # if the user does not have sufficient claims.
            data_models = get_collection('public/state/data_models')
            for data_model in data_models:
                key = data_model['key']
                update_document(
                    f'{model_type}/{organization_id}/data_models/{key}',
                    data_model)

        # Create or update the organization in Firestore.
        entry = {**doc, **data}
        print('Entry:', entry)
        update_document(f'{model_type}/{organization_id}', entry)

        # FIXME:
        # On organization creation, the creating user get custom claims.
        update_custom_claims(uid,
                             claims={
                                 'owner': organization_id,
                                 'team': organization_id
                             })

        # TODO:  Owners can add other users to the team and
        # the receiving user then gets the claims.
        # team: [organization_id, ...]

        # Create activity log.
        changes = [data]
        create_log(f'{model_type}/{uid}/logs',
                   claims=claims,
                   action='Updated organization data.',
                   log_type=model_type,
                   key=f'{model_type}_data',
                   changes=changes)

        return Response({
            'data': entry,
            'success': True
        },
                        content_type='application/json')

    elif request.method == 'DELETE':

        # TODO: Only user's with organization_id in owner claim can delete the organization.

        return Response({'error': 'not_implemented'},
                        content_type='application/json')