Exemplo n.º 1
0
def get_signature(request, *args, **argv):  #pylint: disable=unused-argument
    """Get a user's signature given their pin, using a stored hash of the `pin:uid`.
    Args:
        request (HTTPRequest): A request to get the user's session.
    Returns:
        (JsonResponse): A JSON response containing the user's claims.
    """
    user_claims = verify_session(request)
    uid = user_claims['uid']
    post_data = loads(request.body.decode('utf-8'))
    pin = post_data['pin']
    message = f'{pin}:{uid}'
    app_secret = get_document('admin/api')['app_secret_key']
    code = sha256_hmac(app_secret, message)
    verified_claims = get_document(f'admin/api/pin_hmacs/{code}')
    if not verified_claims:
        return JsonResponse({'error': True, 'message': 'Invalid pin.'})
    elif verified_claims.get('uid') == uid:
        user_settings = get_document(f'users/{uid}')
        return JsonResponse({
            'success':
            True,
            'message':
            'User verified.',
            'signature_url':
            user_settings['signature_url'],
            'signature_created_at':
            user_settings['signature_created_at']
        })
    else:
        return JsonResponse({'error': True, 'message': 'Invalid pin.'})
Exemplo n.º 2
0
def organization_team(request, organization_id=None, user_id=None):
    """Get team member data for an organization, given an authenticated
    request from a member of the organization.
    """
    claims = authenticate_request(request)
    print('Claims:', claims)
    try:
        if organization_id in claims['team']:
            organization_data = get_document(
                f'organizations/{organization_id}')
            team = organization_data['team']
            team_members = []
            if user_id:
                team = [user_id]
            for uid in team:
                team_member = get_document(f'users/{uid}')
                team_members.append(team_member)
            return Response({'data': team_members},
                            content_type='application/json')
        else:
            message = 'You are not a member of the requested organization.'
            return Response({'error': True, 'message': message}, status=403)
    except KeyError:
        message = 'You are not a member of any teams. Try authenticating.'
        return Response({'error': True, 'message': message}, status=401)
Exemplo n.º 3
0
def upload_latest_video(datafile):
    """ Upload the lastest video data.
    Args:
        datafile (str): The path to a .json file containing the video data.
    """

    # Read in the video data.
    with open(datafile) as f:
        data = json.load(f)

    # Get the current count of videos.
    doc = firebase.get_document('public/videos')
    number = doc['total_videos']

    # Upload subscription plan data to Firestore.
    # Only incrementing the number of videos if the video doesn't exist yet.
    for item in data[-1:]:
        doc_id = item['video_id']
        ref = f'public/videos/video_data/{doc_id}'
        existing_doc = firebase.get_document(ref)
        if not existing_doc:
            number += 1
        item['number'] = number
        item['published'] = parser.parse(item['published_at'])
        firebase.update_document(ref, item)

    # Update video statistics.
    firebase.update_document('public/videos', {'total_videos': len(data)})
    return data[-1]
Exemplo n.º 4
0
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # Get PayPal credentials.
        context['paypal'] = get_document('credentials/paypal')

        # Get the page's dataset.
        dataset_id = self.kwargs.get('dataset_id', '')
        if dataset_id:
            context['dataset'] = get_document(
                f'public/data/datasets/{dataset_id}')

        return context
Exemplo n.º 5
0
def initialize_traceability(project_id, license_number, version_id):
    """Initialize a Metrc client.
    Optional: Figure out how to pre-initialize and save Metrc client.
    """

    # Get vendor version number.
    admin_data = get_document('admin/metrc')
    vendor_version_id = admin_data['vendor_api_key_secret']['version_id']

    # Get Vendor API key using secret manager.
    vendor_api_key = access_secret_version(
        project_id=project_id,
        secret_id='metrc_vendor_api_key',
        version_id=vendor_version_id
    )

    # Get user API key using secret manager.
    user_api_key = access_secret_version(
        project_id=project_id,
        secret_id=f'{license_number}_secret',
        version_id=version_id
    )

    track = initialize_metrc(vendor_api_key, user_api_key)
    return track
Exemplo n.º 6
0
def create_user_pin(request, *args, **argv):  #pylint: disable=unused-argument
    """Using a pin for a given user, create and store a hash of the `pin:uid`.
    Args:
        request (HTTPRequest): A request to get the user's session.
    Returns:
        (JsonResponse): A JSON response with a success message.
    """
    user_claims = verify_session(request)
    uid = user_claims['uid']
    post_data = loads(request.body.decode('utf-8'))
    pin = post_data['pin']
    message = f'{pin}:{uid}'
    app_secret = get_document('admin/api')['app_secret_key']
    code = sha256_hmac(app_secret, message)
    post_data = loads(request.body.decode('utf-8'))
    now = datetime.now()
    # Optional: Add expiration to pins
    user_claims['hmac'] = code
    delete_user_pin(request)
    update_document(f'admin/api/pin_hmacs/{code}', user_claims)
    update_document(f'users/{uid}', {'pin_created_at': now.isoformat()})
    create_log(f'users/{uid}/logs', user_claims, 'Created pin.', 'pin',
               'pin_create', [{
                   'created_at': now
               }])
    return JsonResponse({
        'success': True,
        'message': 'Pin successfully created.'
    })
Exemplo n.º 7
0
def users(request):
    """Get, update, or create user's data."""
    try:

        # Authenticate the user.
        claims = authenticate_request(request)
        print('User claims:', claims)
        uid = claims['uid']

        # Get the user's data.
        if request.method == 'GET':
            user_data = get_document(f'users/{uid}')
            response = {'success': True, 'data': user_data}
            return Response(response, content_type='application/json')

        # Edit user data if a 'POST' request.
        post_data = loads(request.body.decode('utf-8'))
        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({'success': True}, content_type='application/json')

    except:
        return Response(
            {'success': False},
            content_type='application/json',
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
Exemplo n.º 8
0
def get_user_from_api_key(api_key):
    """Identify a user given an API key.
    Args:
        api_key (str): An API key to identify a given user.
    Returns:
        (dict): Any user data found, with an empty dictionary if there
            is no user found.
    """
    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']
    user_claims = get_custom_claims(uid)
    user_claims['permissions'] = key_data['permissions']
    user_claims['uid'] = uid
    return user_claims
Exemplo n.º 9
0
def get_user_context(request, context):
    """Get the user-specific context.
    Args:
        request (HTTPRequest): A request to check for a user session.
        context (dict): Existing page context.
    Returns
        context (dict): Page context updated with any user-specific context.
    """
    print('Getting user context by verifying session.')
    try:
        claims = auth.verify_session(request)
    except:
        print('Invalid, not present, or expired session cookie.')
        return context
    if claims:
        uid = claims['uid']
        print('User verified from session cookie:', claims['email'])
        query = {'key': 'team', 'operation': 'array_contains', 'value': uid}
        organizations = get_collection('organizations', filters=[query])
        user_data = get_document(f'users/{uid}')
        context['organizations'] = organizations
        context['user'] = {**claims, **user_data}
    else:
        context['organizations'] = []
        context['user'] = {}
    return context
Exemplo n.º 10
0
 def from_fb(cls, client, ref):
     """Initialize a class from Firebase data.
     Args:
         client (Client): A client instance.
         ref (str): The reference to the document in Firestore.
     """
     data = get_document(ref)
     return cls(client, data)
Exemplo n.º 11
0
def create_coas(request):
    """Generate certificates of analysis."""

    # Authenticate the user.
    claims, status, org_id = authorize_user(request)
    if status != 200:
        return Response(claims, status=status)

    # Get posted samples.
    posted_data = loads(request.body.decode('utf-8'))
    sample_ids = posted_data['sample_ids']

    # Create certificates for each sample.
    data = []
    for sample_id in sample_ids:

        # Get the sample data.
        sample_data = get_document(
            f'organizations/{org_id}/samples/{sample_id}')

        # Get the results for each sample. If there are no results,
        # then get the measurements for each sample and calculate
        # the results for each sample. Add a empty dictionary if missing everything.
        sample_results = get_collection(f'organization/{org_id}/results',
                                        order_by='updated_at',
                                        desc=True,
                                        filters=[{
                                            'key': 'sample_id',
                                            'operation': '==',
                                            'value': sample_id
                                        }])
        if not sample_results:
            sample_results = calculate_results(request)
            if not sample_results:
                sample_results = [{}]

        # Define the certificate context.
        context = {**sample_data, **sample_results[0]}

        # Get the certificate template.
        template_name = sample_data.get('coa_template_ref', DEFAULT_TEMPLATE)

        # Create the PDF, keeping the data.
        # Efficiency gain: Keep the template in /tmp so they don't have
        # to be downloaded each iteration.
        certificate = generate_coas(
            context,
            coa_template=template_name,
            # output_pages=pages,
            # limits=limits
        )
        data.append(certificate)

    # Return list of certificate data.
    return Response({'data': data}, status=200)
Exemplo n.º 12
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)
Exemplo n.º 13
0
def get_user_subscriptions(request):
    """Get a user's subscriptions."""
    claims = authenticate_request(request)
    try:
        user_id = claims['uid']
        subscriptions = get_document(f'subscribers/{user_id}')
        response = {'success': True, 'data': subscriptions}
        return Response(response, content_type='application/json')
    except KeyError:
        response = {'success': False, 'message': 'Invalid authentication.'}
        return Response(response, content_type='application/json')
Exemplo n.º 14
0
def get_model_context(context, organization_id):
    """Get model context based on the current page and section.
    Args:
        request (request): The request object.
        context (dict): A dictionary of existing page context.
    Returns
        context (dict): The context updated with any model context.
    """
    model = context['screen']
    ref = f'organizations/{organization_id}/data_models/{model}'
    context['data_model'] = get_document(ref)
    return context
Exemplo n.º 15
0
def verify_user_pin(request, *args, **argv): #pylint: disable=unused-argument
    """Verify a pin for a given user, using a stored hash of the `pin:uid`.
    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']
    post_data = loads(request.body.decode('utf-8'))
    pin = post_data['pin']
    message = f'{pin}:{uid}'
    app_secret = get_document('admin/api')['app_secret_key']
    app_salt = get_document('admin/api')['app_salt']
    code = sha256_hmac(app_secret, message + app_salt)
    verified_claims = get_document(f'admin/api/pin_hmacs/{code}')
    if verified_claims.get('uid') == uid:
        token = create_custom_token(uid, claims={'pin_verified': True})
        return JsonResponse({'success': True, 'message': 'User verified.', 'token': token})
    else:
        return JsonResponse({'error': True, 'message': 'Invalid pin.'})
Exemplo n.º 16
0
def state_data(request, state=None):
    """Get or update data for a given state."""
    # Optional: Allow authenticated users to edit state data?
    # claims = auth.authenticate_request(request)
    # if request.method == 'GET':
    # if request.method == 'POST':
    #     uid = claims['uid']
    if state:
        data = get_document(f'public/data/state_data/{state}')
    else:
        data = get_collection('public/data/state_data', order_by='state')
    response = {'success': True, 'message': '', 'data': data}
    return Response(response)
Exemplo n.º 17
0
def approve_coas(request):
    """Approve certificates of analysis for release after they have
    been reviewed."""

    # Authenticate the user.
    claims = authenticate_request(request)
    # FIXME: Get `org_id`
    org_id = None
    if claims.get('user') is None:
        message = 'Authentication failed.'
        return Response({'success': False, 'data': message}, status=401)

    # Restrict approving certificates to QA and owners.
    qa = claims.get('qa', [])
    owner = claims.get('owner', [])
    if org_id not in owner and org_id not in qa:
        message = f'Your must be an owner or quality assurance manager of this organization for this operation.'
        return Response({'error': True, 'message': message}, status=403)

    # Require pin.
    uid = claims['uid']
    post_data = loads(request.body.decode('utf-8'))
    pin = post_data['pin']
    message = f'{pin}:{uid}'
    app_secret = get_document('admin/api')['app_secret_key']
    code = sha256_hmac(app_secret, message)
    verified_claims = get_document(f'admin/api/pin_hmacs/{code}')
    if not verified_claims:
        return JsonResponse({'error': True, 'message': 'Invalid pin.'})
    elif verified_claims.get('uid') != uid:
        return JsonResponse({'error': True, 'message': 'Invalid pin.'})

    # Call generate_coas
    # - Make sure to fill-in approvers signature.
    # Update the sample's certificate_status.

    return NotImplementedError
Exemplo n.º 18
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 = authenticate_request(request)
    uid = user_claims['uid']
    api_key = token_urlsafe(48)
    app_secret = get_document('admin/api')['app_secret_key']
    app_salt = get_document('admin/api')['app_salt']
    code = sha256_hmac(app_secret, api_key + app_salt)
    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})
Exemplo n.º 19
0
def lab_data(request, license_number=None):
    """Get laboratory information (public API endpoint)."""
    data = []
    if request.method == 'GET':

        # Get a specific organization.
        organization_id = request.query_params.get('organization_id')
        if organization_id and organization_id != 'undefined':
            data = get_document(f'public/data/labs/{organization_id}')

        else:

            # Define query parameters.
            filters = []
            order_by = request.query_params.get('order_by', 'name')
            limit = request.query_params.get('limit')
            state = request.query_params.get('state')
            # Optional: Implement more queries the user can use.
            # - name
            # - analyses?

            # Apply user-specified filters.
            if license_number:
                filters.append({
                    'key': 'license',
                    'operation': '==',
                    'value': license_number
                })
            elif state:
                filters.append({
                    'key': 'state',
                    'operation': '==',
                    'value': state
                })

            # Query and return the docs.
            data = get_collection(
                'public/data/labs',
                desc=False,
                filters=filters,
                limit=limit,
                order_by=order_by,
            )

    # Return data in a response.
    response = {'success': True, 'data': data}
    return Response(response, status=200)
Exemplo n.º 20
0
def get_user_data(request: Any, context: dict) -> dict:
    """Get user-specific context.
    Args:
        request (HTTPRequest): A request to check for a user session.
        context (dict): Existing page context.
    Returns
        context (dict): Page context updated with any user-specific context.
    """
    claims = authenticate_request(request)
    try:
        uid = claims['uid']
        query = {'key': 'team', 'operation': 'array_contains', 'value': uid}
        organizations = get_collection('organizations', filters=[query])
        user_data = get_document(f'users/{uid}')
        context['organizations'] = organizations
        context['user'] = {**claims, **user_data}
    except KeyError:
        context['organizations'] = []
        context['user'] = {}
    return context
Exemplo n.º 21
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.'})
Exemplo n.º 22
0
def get_page_data(context: dict) -> dict:
    """Get all data for a page from Firestore.
    Args:
        context (dict): A dictionary of existing page context.
    Returns
        (dict): The context updated with any page-specific data.
    """
    namespaces = []
    try:
        namespace = context['page']
        namespaces.append(page_data[namespace])
    except KeyError:
        pass
    try:
        namespace = context['section']
        namespaces.append(page_data[namespace])
    except KeyError:
        pass
    for namespace in namespaces:
        try:
            documents = namespace['documents']
            for item in documents:
                context[item['name']] = get_document(item['ref'])
        except KeyError:
            pass
        try:
            collections = namespace['collections']
            for item in collections:
                context[item['name']] = get_collection(
                    item['ref'],
                    limit=item.get('limit'),
                    order_by=item.get('order_by'),
                    desc=item.get('desc'),
                    filters=item.get('filters'),
                )
        except KeyError:
            pass
    return context
Exemplo n.º 23
0
def analysis_data(request, analysis_id=None):
    """Get data about lab analyses (public API endpoint)."""
    data = []
    collection = 'public/data/analyses'
    if request.method == 'GET':

        # Get a specific observation.
        if analysis_id is not None:
            data = get_document(f'{collection}/{analysis_id}')

        # Otherwise query observations.
        else:

            # Define query parameters.
            filters = []
            order_by = request.query_params.get('order_by', 'name')
            limit = request.query_params.get('limit')

            # Optional: Implement more queries the user can use.
            # Apply user-specified filters.
            # param = request.query_params.get('param')
            # if param:
            #     filters.append({'key': 'param', 'operation': '==', 'value': param})

            # Query and return the docs.
            data = get_collection(
                collection,
                desc=False,
                filters=filters,
                limit=limit,
                order_by=order_by,
            )

    # Return the data.
    response = {'success': True, 'data': data}
    return Response(response, status=200)
Exemplo n.º 24
0
def review_coa(env_file='.env', signature_dest='./tmp/signature.png'):
    """
    Creates a certificate of analysis.
    """
    # TODO: Validate the user's pin to get their UID.
    uid = ''

    # Get the user's signature (if not already downloaded?).
    env = environ.Env()
    env.read_env(env_file)
    bucket_name = env('FIREBASE_STORAGE_BUCKET')
    signature_data = get_document(f'users/{uid}/user_settings/signature')
    download_file(bucket_name, signature_data['signature_ref'], signature_dest)

    # Insert the signature into the CoA template.

    # Create the PDF.

    # Upload the PDF to storage.

    # Save the reviewer data.

    print('Reviewing CoA...')
    return NotImplementedError
Exemplo n.º 25
0
 def get_context_data(self, **kwargs):
     context = super().get_context_data(**kwargs)
     credentials = get_document('admin/google')
     api_key = credentials['public_maps_api_key']
     context['api_key'] = [api_key]
     return context
Exemplo n.º 26
0
    video_data['storage_url'] = firebase.get_file_url(destination, bucket_name)
    video_data['short_link'] = firebase.create_short_url(
        api_key, video_data['storage_url'])
    firebase.update_document(destination, video_data)
    return video_data


if __name__ == '__main__':

    # Initialize Firebase and get the Firebase project API key.
    env = environ.Env()
    env.read_env('../../.env')
    credentials = env('GOOGLE_APPLICATION_CREDENTIALS')
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials
    db = firebase.initialize_firebase()
    api_key = firebase.get_document('admin/firebase')['web_api_key']
    bucket_name = env('FIREBASE_STORAGE_BUCKET')

    # Upload a video to Firebase storage, saving the metadata to Firestore.
    video_dir = env('VIDEO_DIR')
    file_name = 'cannabis-data-science-episode-1'
    extension = '.mov'
    video_data = {
        'music': ['Vivaldi'],
        'category': 'Category: Science and Technology',
        'title': 'Data Wrangling | Cannabis Data Science Episode 1',
        'description':
        'Join the fun, zany bunch on our first Cannabis Data Science meetup as we begin to wrangle the firehose of data that the Washington State traceability system offers to the public. Support the group: https://opencollective.com/cannlytics-company Find the data and source code: https://github.com/cannlytics/cannabis-data-science',
        'published_at': '2021-02-24',
        'video_id': 'cannabis-data-science-episode-1',
        'size': '1.21GB',
Exemplo n.º 27
0
def unsubscribe(request):
    """Unsubscribe a user from a PayPal subscription."""

    # Authenticate the user.
    claims = authenticate_request(request)
    try:
        uid = claims['uid']
        user_email = claims['email']
    except KeyError:
        response = {'success': False, 'message': 'Unable to authenticate.'}
        return JsonResponse(response)

    # Get the subscription they wish to unsubscribe from.
    data = loads(request.body)
    plan_name = data['plan_name']

    # Unsubscribe the user with the PayPal SDK.
    try:
        project_id = os.environ['GOOGLE_CLOUD_PROJECT']
        payload = access_secret_version(project_id, 'paypal', 'latest')
        paypal_secrets = loads(payload)
        paypal_client_id = paypal_secrets['client_id']
        paypal_secret = paypal_secrets['secret']
        paypal_access_token = get_paypal_access_token(paypal_client_id,
                                                      paypal_secret)
        user_data = get_document(f'users/{uid}')
        subscription_id = user_data[f'{plan_name}_subscription_id']
        cancel_paypal_subscription(paypal_access_token, subscription_id)
        updated_user_data = {'support': False}
        updated_user_data[f'{plan_name}_subscription_id'] = ''
        update_document(f'users/{uid}', updated_user_data)
    except:
        subscription_id = 'Unidentified'

    # Notify the staff.
    staff_message = """Confirm that the following subscription has been canceled:
    User: {}
    Email: {}
    Plan: {}
    Subscription ID: {}
    """.format(uid, user_email, plan_name, subscription_id)
    send_mail(
        subject='User unsubscribed from a PayPal subscription.',
        message=staff_message,
        from_email=DEFAULT_FROM_EMAIL,
        recipient_list=[DEFAULT_FROM_EMAIL],
        fail_silently=False,
    )

    # Create an activity log.
    create_log(
        ref='logs/website/subscriptions',
        claims=claims,
        action=f'User ({user_email}) unsubscribed from {plan_name}.',
        log_type='subscription',
        key='subscribe',
        changes=data,
    )

    # Return a success message.
    message = 'Successfully unsubscribed from subscription.'
    response = {'success': True, 'message': message}
    return JsonResponse(response)
Exemplo n.º 28
0
def import_data(request):
    """Import data from an Excel worksheet for a given data model.
    TODO: Limit the size / rate of downloads (tie to account usage / billing).
    Optional: Handle .csv imports.
    Optional: Submit form without refresh.
    """
    # Authenticate the user.
    session_cookie = request.COOKIES.get('__session')
    claims = verify_session_cookie(session_cookie)
    if not claims:
        return HttpResponse(status=401)

    # Get the import parameters and file, validating the file.
    # Also, authorize that the user is part of the organization.
    model = request.GET.get('model')
    org_id = request.GET.get('organization_id')
    excel_file = request.FILES['excel_file']
    ext = excel_file.name.split('.').pop()
    if excel_file.size >= 1024 * 1000 * 500:
        return JsonResponse({
            'error': True,
            'message': 'File too large.'
        },
                            status=406)
    if ext not in ['csv', 'xlsx', 'xlsm']:
        return JsonResponse(
            {
                'error': True,
                'message': 'Expected a .csv, .xlsx, or .xlsm file.'
            },
            status=406)
    if org_id not in claims.get('team', []):
        return HttpResponse(
            {
                'error': True,
                'message': 'You are not a member of this organization.'
            },
            status=403)

    # Read the data from Excel.
    data = read_worksheet(excel_file)

    # Get singular from data models, to identify the ID.
    data_model = get_document(f'organizations/{org_id}/data_models/{model}')
    model_singular = data_model['singular']

    # Clean data according to data type.
    # Optional: Add more validation / data cleaning by type.
    for field in data_model['fields']:
        key = field['key']
        data_type = field.get('type', 'text')
        if data_type == 'text' or data_type == 'textarea':
            data[key].replace(['0', '0.0', 0], '', inplace=True)

    # Save imported data to Firestore (FIXME: in reverse order for user sanity).
    updated_at = datetime.now().isoformat()
    for key, row in data.iterrows():
        # data = data.replace({np.nan: None})
        # for idx in reversed(data.index):
        # row = data.loc[idx]
        doc_id = row[f'{model_singular}_id']
        if doc_id:
            values = row.to_dict()
            values['updated_at'] = updated_at
            values['updated_by'] = claims['uid']
            update_document(f'organizations/{org_id}/{model}/{doc_id}', values)

    # Submit the form (FIXME: preferably without refreshing).
    # See: https://stackoverflow.com/questions/11647715/how-to-submit-form-without-refreshing-page-using-django-ajax-jquery
    return HttpResponseRedirect(f'/{model}')
Exemplo n.º 29
0
def labs(request, format=None):
    """Get or update information about labs."""

    # Query labs.
    if request.method == 'GET':
        limit = request.query_params.get('limit', None)
        order_by = request.query_params.get('order_by', 'state')
        # TODO: Get any filters from dict(request.query_params)
        labs = get_collection('labs',
                              order_by=order_by,
                              limit=limit,
                              filters=[])
        return Response({'data': labs}, content_type='application/json')

    # Update a lab given a valid Firebase token.
    elif request.method == 'POST':

        # Check token.
        try:
            claims = auth.authenticate(request)
        except:
            return Response({'error': 'Could not auth.authenticate.'},
                            status=status.HTTP_400_BAD_REQUEST)

        # Get the posted lab data.
        lab = request.data
        org_id = lab['id']
        lab['slug'] = slugify(lab['name'])

        # TODO: Handle adding labs.
        # Create uuid, latitude, and longitude, other fields?

        # Determine any changes.
        existing_data = get_document(f'labs/{org_id}')
        changes = []
        for key, after in lab:
            before = existing_data[key]
            if before != after:
                changes.append({'key': key, 'before': before, 'after': after})

        # Get a timestamp.
        timestamp = datetime.now().isoformat()
        lab['updated_at'] = timestamp

        # Create a change log.
        log_entry = {
            'action': 'Updated lab data.',
            'type': 'change',
            'created_at': lab['updated_at'],
            'user': claims['uid'],
            'user_name': claims['display_name'],
            'user_email': claims['email'],
            'photo_url': claims['photo_url'],
            'changes': changes,
        }
        update_document(f'labs/{org_id}/logs/{timestamp}', log_entry)

        # Update the lab.
        update_document(f'labs/{org_id}', lab)

        return Response(log_entry, status=status.HTTP_201_CREATED)
Exemplo n.º 30
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')