Firebase Auth + Python backend - python

I am going to use Firebase Auth and Database modules to create my web app. However, not all things that I want my app to do is possible to achieve on only front end. So I want to also use backend with Python's Bottle framework to handle requests and Pyrebase to get access to Firebase Database.
Let's say that after logging in I need to go to mainpage and see personalized content, for example my notes. They are structured this way in DB:
{
"notes": [{
"id": "1",
"title": "X",
"author": "user1"
},
{
"id": "2",
"title": "Y",
"author": "user2"
} and so on... ]
}
So how it's possible to implement showing only my articles on main page?
I understand that I need to filter my notes based on author value, but how to let Bottle understand who is currently logged in?
I've read there, that I should somehow send unique token to backend server to authenticate current user, but how to do that? Inserting Token in every link as GET parameter seems to be silly, but I see no other way to implement that.

Start by organizing your database so that each note becomes a child object:
{
"notes": {
"id1": {
"id": "id1",
"title": "X",
"author": "user1",
},
"id2": {
}
}
}
Then this particular interaction can be implemented entirely in the client-side. Just execute a query to filter the notes you want. For example in a JS client:
var uid = firebase.auth().currentUser.uid;
var query = ref.orderByChild('author').equalTo(uid);
// Listen for query value events
If you want to run this on a backend server, and you want to ensure that only logged in users are allowed to execute it, then you must pass the ID token from the client app to the server on each request. Here's how to implement the server-side logic using the Python Admin SDK:
import firebase_admin
from firebase_admin import auth
from firebase_admin import db
token = '....' # Extract from the client request
try:
decoded = auth.verify_id_token(token)
uid = decoded.uid
ref = db.reference('path/to/notes')
notes = ref.order_by_child('author').equal_to(uid).get()
# Process notes response
except ValueError as ex:
print(ex)
# Send error to client

Related

Is there a module/method in Python to help identify if a Google Account is a regular account or a Google Workspace-linked account?

A little more detail on the question -
Scenario
The app I'm working on currently performs the following -
Logs in users via Google OAuth ( added to Auth0 login )
Comprises of a list of Google Sheets with their links, which the user can open when he is logged in
When the user clicks on a sheet's link to open it, he is redirected to a page where the sheet is expected to be displayed in an iframe.
The gspread module in Python retrieves the list of users the sheet has been shared with (permission list) (gspread is authenticated using a service account which helps do this). If the authenticated user is a part of the permission list, the iframe is displayed, else, an error message is displayed.
Now, the next requirement we'd like to achieve is for specific users in the site to be able to share the Google Sheet with other users, using the share method in the gspread module. However, we would like to share it with users with regular Google accounts, and not those enabled with Google Workspace, owing to business requirements which I prefer not to disclose at this point.
Is there a way to do this? I've found a something here - https://developers.google.com/admin-sdk/directory/v1/quickstart/python#configure_the_sample, but this is only to check with the users of the same workspace, if the service account I possess is that of the workspace's admin, but what I need to know is in general if a given account is a regular one or is linked to the workspace of any organization.
The People api has a method called people.get If i pass it me and check the person fields for memberships
Workspace domain account
{
"resourceName": "people/106744391248434652261",
"etag": "%EgMBLjcaBAECBQciDFpMNzJsdkk3SG80PQ==",
"memberships": [
{
"metadata": {
"source": {
"type": "DOMAIN_PROFILE",
"id": "106744391248434652261"
}
},
"domainMembership": {
"inViewerDomain": true
}
}
]
}
standard gmail user
{
"resourceName": "people/117200475532672775346",
"etag": "%EgMBLjcaBAECBQciDEdwc0JEdnJyNWRnPQ==",
"memberships": [
{
"metadata": {
"source": {
"type": "CONTACT",
"id": "3faa96eb08baa4be"
}
},
"contactGroupMembership": {
"contactGroupId": "myContacts",
"contactGroupResourceName": "contactGroups/myContacts"
}
}
]
}
So the answer is yes you need to go though the google people api. I dont have any python examples for the people api on hand but let me know if you cant get it working.

Configure diagnostic setting for azure database using Python SDK

I want to configure diagnostic setting for Azure database using Python. I know that I have to use DiagnosticSettingsOperations Class, and MonitorManagementClient Client, and create_or_update method to start. I am fairly new to Python development, and I am struggling to put the pieces together.
However, there is no proper examples on what parameters to pass for the DiagnosticSettingsOperations Class.
Sample code:
from azure.mgmt.monitor import MonitorManagementClient
from azure.identity import ClientSecretCredential
####### FUNCTION TO CREATE AZURE AUTHENTICATION USING SERVICE PRINCIPAL #######
def authenticateToAzureUsingServicePrincipal():
# Authenticate to Azure using Service Principal credentials
client_id = 'client_id'
client_secret = 'client_secret'
client_tenant_id = 'client_tenant_id'
# Create Azure credential object
servicePrincipalCredentialObject = ClientSecretCredential(tenant_id=client_tenant_id, client_id=client_id, client_secret=client_secret)
return servicePrincipalCredentialObject
azureCredential = authenticateToAzureUsingServicePrincipal()
monitorManagerClient = MonitorManagementClient(azureCredential)
I want to configure Diagnostic setting for Azure sql database, which selects ALL Metrics and Logs by default and sends to a Log analytics workspace. Does anyone know how to proceed further?
The code looks like below:
#other code
monitorManagerClient = MonitorManagementClient(azureCredential)
# Creates or Updates the diagnostic setting[put]
BODY = {
"workspace_id": "the resource id of the log analytics workspace",
"metrics": [
{
"category": "Basic",
"enabled": True,
"retention_policy": {
"enabled": False,
"days": "0"
}
}
#other categories
],
"logs": [
{
"category": "SQLInsights",
"enabled": True,
"retention_policy": {
"enabled": False,
"days": "0"
}
}
#other categories
],
# "log_analytics_destination_type": "Dedicated"
}
diagnostic_settings = self.mgmt_client.diagnostic_settings.create_or_update(RESOURCE_URI, INSIGHT_NAME, BODY)
There is an example in github, you can take a look at it. And if you want to select ALL Metrics and Logs, you should add them one by one in the metrics / logs in the BODY in the above code.

How to publish a View to Slack app for all users?

I have a Slack App that is connected to an AWS Lex bot. Thus, the Request URL for the Slack app is the Postback URL provided by Lex. However, I want to add a Home tab for the app, but am unable to publish a surface for all users. The Slack API only seems to allow you to publish a surface for a specific user (user_id is a required parameter for the POST call). How can I publish this view to all users who use the app?
publish_url = "https://slack.com/api/views.publish"
header = {'content-type':'application/json'}
parameters = {
"token": slack_token,
"user_id": member_id, # <--- This is my problem
"view": json.dumps({
"type": "home",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Welcome to *Survey Analyzer*! ..."
}
},
...
]
})
}
r = requests.post(publish_url, params=parameters, headers=header)
When I use my own member ID, it pushes the view properly to me and me only. I've also tried using the app's ID, but that doesn't seem to push the view to any users.
You can only publish a home tab for each user separately. You can use users.list to retrieve all users of a workspace and then call views.publish for each user in that list.
Alternatively you can subscribe to the app_home_opened event through the Events API. Once a user opens the home tab it will fire an event including the user's id. That way you'll only publish a view for a user who is actually visiting the home tab of your app.

Authentication to use for user notifications using Crossbar/Autobahn?

I'm currently trying to implement a user notification system using Websockets via Crossbar/Autobahn. I have done multiple tests and gone through the documentation, however, I'm not sure if there's a solution to having the following workflow work:
User signs in with web app -- this is done through JWT
Frontend establishes a websocket connection to a running crossbar instance.
Frontend attempts to subscribe to a URI specifically for the user's notifications: i.e. com.example.notifications.user.23 or com.example.user.23.notifications'. Where23` is the user id.
User's JWT is checked to see if user is allowed to access subscription.
When activity is generated and causes a notification, the backend publishes the user-specific URIs.
For step 3, I can't tell if the current support auth methods have what I need. Ideally, I would like an auth method which I can customize (in order to implement a JWT authenticator within Crossbar) that I can apply to a URI pattern, but NOT give access to the entire pattern to the subscribing user. This is partially solved by the dynamic auth methods, but is missing the latter half:
For example (my ideal workflow):
User attempts to subscribe to a URI com.example.user.23.notifications.
URI matches com.example.user..notifications (wildcard pattern in http://crossbar.io/docs/Pattern-Based-Subscriptions/)
Auth token is validated and user is given access to only com.example.user.23.notifications.
Is the above achievable in a simple way? From what I can tell, it may only be possible if I somehow generate a .crossbar/config.json which contains URI permutations of all user ids...and automatically generate a new config for each new user -- which is completely not a reasonable solution.
Any help is appreciated!
Use authorizer.
See http://crossbar.io/docs/Authorization/#dynamic-authorization
Register a dynamic authorizer for the user role that session was assigned when joining/authenticating:
{
"name": "authorizer",
"permissions": [
{
"uri": "com.example.authorize",
"register": true
}
]
},
{
"name": "authenticator",
"permissions": [
{
"uri": "com.example.authenticate",
"register": true
}
]
},
{
"name": "user",
"authorizer": "com.example.authorize"
},
...
"components": [
{
"type": "class",
"classname": "example.AuthenticatorSession",
"realm": "realm1",
"role": "authenticator",
"extra": {
"backend_base_url": "http://localhost:8080/ws"
}
},
{
"type": "class",
"classname": "example.AuthorizerSession",
"realm": "realm1",
"role": "authorizer"
}
]
Write a class
class AuthorizerSession(ApplicationSession):
#inlineCallbacks
def onJoin(self, details):
print("In AuthorizerSession.onJoin({})".format(details))
try:
yield self.register(self.authorize, 'com.example.authorize')
print("AuthorizerSession: authorizer registered")
except Exception as e:
print("AuthorizerSession: failed to register authorizer procedure ({})".format(e))
def authorize(self, session, uri, action):
print("AuthorizerSession.authorize({}, {}, {})".format(session, uri, action))
if session['authrole'] == u'backend': # backnend can do whatever
return True
[Authorization logic here]
return authorized

Authenticating with LinkedIn using django-all-access

I'm using django-all-access to implement OAuth authentication for Facebook, Twitter, and LinkedIn. Facebook and Twitter are working fine, LinkedIn is redirecting me to the wrong page.
Here's my setup (consumer keys and secrets are obviously obfuscated):
[
{
"pk": null,
"model": "allaccess.provider",
"fields": {
"name": "facebook",
"consumer_key": "xxx",
"consumer_secret": "xxx",
"authorization_url": "https://www.facebook.com/dialog/oauth",
"access_token_url": "https://graph.facebook.com/oauth/access_token",
"request_token_url": "",
"profile_url": "https://graph.facebook.com/me"
}
},
{
"pk": null,
"model": "allaccess.provider",
"fields": {
"name": "twitter",
"consumer_key": "xxx",
"consumer_secret": "xxx",
"authorization_url": "https://api.twitter.com/oauth/authenticate",
"access_token_url": "https://api.twitter.com/oauth/access_token",
"request_token_url": "https://api.twitter.com/oauth/request_token",
"profile_url": "https://api.twitter.com/1.1/account/verify_credentials.json"
}
},
{
"pk": null,
"model": "allaccess.provider",
"fields": {
"name": "linkedin",
"consumer_key": "xxx",
"consumer_secret": "xxx",
"authorization_url": "https://www.linkedin.com/uas/oauth2/authorization",
"access_token_url": "https://www.linkedin.com/uas/oauth2/accessToken",
"request_token_url": "",
"profile_url": "https://api.linkedin.com/v1/people/~"
}
}
]
Both Facebook and Twitter are using the correct authentication flow and registering users properly, but Twitter redirects me to the wrong page and is not registering users at all. Here's the LinkedIn flow (I removed most parameters, and left the redirect_uri):
https://www.linkedin.com/uas/oauth2/authorization?redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Faccounts%2Fcallback%2Flinkedin%2F&response_type=code
http://localhost:8000/accounts/callback/linkedin/
http://localhost:8000/accounts/login/
My first guess would be that my app settings are improperly configured in LinkedIn, so here are my settings:
OAuth 2.0 Redirect URLs: http://localhost:8000/accounts/callback/linkedin/,http://localhost:8000/accounts/profile/
OAuth 1.0 Accept Redirect URL: http://localhost:8000/accounts/profile/
My second guess would be that the profile_url parameter is wrong, which is https://api.linkedin.com/v1/people/~.
Can anybody help?
Best.
There were two things wrong with this. First, LinkedIn expects the access_token parameter to be named oauth2_access_token, which is not compliant with the RFC 6750. Also, LinkedIn does not return JSON by default, which is expected by the allaccess clients. As such, you'd also need to add format=json as a parameter in the call.
This can be achieved mostly by customising the OAuth2Client.request method, but in my case I went a little further. The allaccess framework sends the access token as a query parameter, which is usually discouraged because the tokens are then logged on the server, which might not be safe. Instead, both OAuth 1 and 2 support sending the token in the Authorization request header. OAuth 1 is a bit more complicated, while OAuth 2 requires only a bearer token.
As such, I customised the OAuth2Client class to handle both these situations.
from allaccess.clients import OAuth2Client as _OAuth2Client
from requests.api import request
class OAuth2Client(_OAuth2Client):
def request(self, method, url, **kwargs):
user_token = kwargs.pop('token', self.token)
token, _ = self.parse_raw_token(user_token)
if token is not None:
# Replace the parent method so the token is sent on the headers. This is
# safer than using query parameters, which is what allaccess does
headers = kwargs.get('headers', {})
headers['Authorization'] = self.get_authorization_header(token)
kwargs['headers'] = headers
return request(method, url, **kwargs)
def get_authorization_header(self, token):
return 'Bearer %s' % (token,)
class OAuth2LinkedInClient(OAuth2Client):
def request(self, method, url, **kwargs):
# LinkedIn does not return JSON by default
params = kwargs.get('params', {})
params['format'] = 'json'
kwargs['params'] = params
return super(OAuth2LinkedInClient, self).request(method, url, **kwargs)
OAuth2Client now sends the access token in the request headers instead of the query parameters. Also, the LinkedIn client adds the format query parameter and sets it to json. There's no need to replace OAuth 1 authentication as it already sends the token in the headers.
Unfortunately, that's not the whole deal. We now need to let allaccess know to use these clients, and we do that by customising the views. Here's my implementation:
from allaccess.views import OAuthRedirect as _OAuthRedirect
from allaccess.views import OAuthCallback as _OAuthCallback
from allaccess.views import OAuthClientMixin as _OAuthClientMixin
from django.core.urlresolvers import reverse
from authy.clients import OAuth2Client, OAuth2LinkedInClient
class OAuthClientMixin(_OAuthClientMixin):
def get_client(self, provider):
# LinkedIn is... Special
if provider.name == 'linkedin':
return OAuth2LinkedInClient(provider)
# OAuth 2.0 providers
if not provider.request_token_url:
return OAuth2Client(provider)
# Let allaccess chose other providers (those will be mostly OAuth 1)
return super(OAuthClientMixin, self).get_client(provider)
class OAuthRedirect(OAuthClientMixin, _OAuthRedirect):
# This is necessary because we'll be setting these on our URLs, we can no longer
# use allaccess' URLs.
def get_callback_url(self, provider):
return reverse('authy-callback', kwargs={ 'provider': provider.name })
class OAuthCallback(OAuthClientMixin, _OAuthCallback):
# We need this. Notice that it inherits from our own client mixin
pass
Now set the URLs to map to our own implementation:
from django.conf.urls import url
from .views import OAuthRedirect, OAuthCallback
urlpatterns = [
url(r'^login/(?P<provider>(\w|-)+)/$', OAuthRedirect.as_view(), name='authy-login'),
url(r'^callback/(?P<provider>(\w|-)+)/$', OAuthCallback.as_view(), name='authy-callback'),
]
There's one other issue left unsolved, however. The problem is that other classes also use clients. I could find the allaccess.models.AccountAccess.api_client method, for instance. I'm not sure if there are more. Now the problem is that our views might be using our clients, while other classes are using different clients. I'm not sure to what extend this could be a problem, but so for it has not bitten me and for now I'm proceeding with this code.
Finally, I would like to credit and thank Mark Lavin, the creator of the allaccess framework. I contacted him and it was his guidance which lead me to these conclusions.
Hope this helps someone else as well!
Farewell.

Categories