How to call Snowflake procedure in AWS Lambda - amazon-web-services

I've got a problem with my Lambda code. It can't execute the procedure in Snowflake. What I'm doing wrong? I can't call the procedure ;(
import snowflake.connector
import boto3
import json
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def run_query(con, query):
cursor = con.cursor();
cursor.execute(query);
cursor.close();
# function to receive a secret value from secrets manager
def get_secret(secret_name, region_name):
session = boto3.session.Session()
client = session.client(service_name="secretsmanager", region_name=region_name)
try:
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
except Exception as e:
raise e
else:
if "SecretString" in get_secret_value_response:
secret = get_secret_value_response["SecretString"]
else:
secret = None
return json.loads(secret)
def lambda_handler(event, context):
# retrieve secret_name from environment configuration
try:
secret_name = os.environ['secret_name']
except KeyError:
# default secret if not set by the infrastructure
secret_name = "snowflake-login"
region_name = os.environ['AWS_DEFAULT_REGION']
# retreive the Secret, assign connection variables
sf_params = get_secret(secret_name, region_name)
try:
PASSWORD = sf_params["sfPassword"]
USER = sf_params["sfUser"]
ACCOUNT = sf_params["sfAccount"]
WAREHOUSE = sf_params["sfWarehouse"]
DATABASE = sf_params["sfDatabase"]
SCHEMA = sf_params["sfSchema"]
ROLE = sf_params["sfRole"]
except KeyError:
error_message = "Missing connection properties in the Secret"
raise ValueError(error_message)
# connect to Snowflake
con = snowflake.connector.connect(user=USER, account=ACCOUNT, password=PASSWORD, warehouse=WAREHOUSE,
database=DATABASE, schema=SCHEMA, role=ROLE)
statment5 = "call MY_SCHEMA.PROCEDURE_LOAD_CSV('MY_PARAMETER')"
run_query(con, statment5)
logger.info(statment5)

Related

flask_login, login_manager.user_loader is not getting called

I am using flask_login, It worked in local but when i moved to different server it is did not work. On trying to debug i realized login_manager.user_loader is not getting on remote machine but the same worked in my local.
This is issue is specific to okta sso authentication on remote machine only. Post okta authentication, i am calling below to save data in session.
login_user(user)
Post this, i was expecting user_loaded to be called but that is not happening. Tried going through document but could not understand. Can you anyone help me as why user_loader is getting called in my local machine but not on remote machine.
init.py
import os
from flask import Flask
from flask_login import LoginManager
from config import app_config
login_manager = LoginManager()
def create_app(config_name):
app = Flask(__name__,
instance_path=os.path.join(os.path.abspath(os.curdir), 'instance'),
instance_relative_config=True)
app.config.from_object(app_config[config_name])
app.config.from_pyfile('config.py')
login_manager.init_app(app)
login_manager.login_message = 'You must be logged in to access this page'
login_manager.login_view = 'admin.login'
login_manager.login_message_category = 'info'
login_manager.session_protection = "strong"
#login_manager.refresh_view ='admin.login'
#login_manager.needs_refresh_message = (u"To protect your account, please reauthenticate to access this page.")
from .home import home as home_blueprint
app.register_blueprint(home_blueprint)
from .restapi import restapi as restapi_blueprint
app.register_blueprint(restapi_blueprint, url_prefix='/steps/api/v1')
return app
In my blueprint view, i am calling login_user to set current user. I have placed user_loader in one seperate file.
from flask import current_app
from flask_login import UserMixin
from app import login_manager, get_connection
from .encryption import encrypt_json, encrypt_password, decrypt_password # noqa
class User(UserMixin):
"""Custom User class."""
id = None
name = None
email = None
description = None
role = None
def __init__(self, id_, name, email, description, role):
print(name)
self.id = str(id_)
self.name = name
self.email = email
self.description = description
self.role = role
def __repr__(self):
return self.name
def claims(self):
"""Use this method to render all assigned claims on profile page."""
return {'name': self.name,
'email': self.email}.items()
#staticmethod
def get(user_id):
try:
connection = get_connection()
user_sql = "SELECT ID, USER_NAME, DESCRIPTION, EMAIL, ROLE FROM USER WHERE ID = {}"
cursor = connection.cursor()
cursor.execute(user_sql.format(user_id))
data = cursor.fetchone()
if data:
return User(id_=data[0], name=data[1], email=data[3], description=data[2], role=data[4])
else:
return None
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
# print(user_id)
# print(USERS_DB)
# return USERS_DB.get(user_id)
#staticmethod
def getByName(user_name):
try:
connection = get_connection()
user_sql = "SELECT ID, USER_NAME, DESCRIPTION, EMAIL, ROLE FROM USER WHERE USER_NAME = '{}' "
cursor = connection.cursor()
cursor.execute(user_sql.format(user_name))
data = cursor.fetchone()
if data:
return User(id_=data[0], name=data[1], email=data[3], description=data[2], role=data[4])
else:
return None
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
#staticmethod
def getByEmail(emailid):
try:
connection = get_connection()
user_sql = "SELECT ID, USER_NAME, DESCRIPTION, EMAIL, ROLE FROM USER WHERE EMAIL = '{}' "
cursor = connection.cursor(buffered=True)
cursor.execute(user_sql.format(emailid))
data = cursor.fetchone()
print(data)
if data:
return User(id_=data[0], name=data[1], email=data[3], description=data[2], role=data[4])
else:
return None
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
#staticmethod
def verify_password(username, password):
try:
# mysql_hook = MySqlHook(mysql_conn_id="STEPS", schema="STEPS")
# connection = mysql_hook.get_conn()
connection = get_connection()
cursor = connection.cursor()
encFlag, decryptedPassword = decrypt_password(password)
lognReqTuple = (username, password)
user_sql = "SELECT USER_NAME, PASSWORD from USER WHERE USER_NAME = '{}'"
cursor.execute(user_sql.format(username))
all_users = cursor.fetchall()
print(all_users)
for user in all_users:
encFlag, decryptedPassword = decrypt_password(user[1]) if user[1] else (False, password)
existingUserTuple = user[0], decryptedPassword
if existingUserTuple == lognReqTuple:
# logging.info('User {} authenticated.'.format(username))
return 0
# logging.info('Un Authorized access, user authentication failed.')
return 1
except Exception as ex:
# logging.error('Error in verifying login details :{}'.format(ex))
print(ex)
return 1
finally:
cursor.close()
connection.close()
#staticmethod
def create(user_id, name, email):
# USERS_DB[user_id] = User(user_id, name, email)
pass
#login_manager.user_loader
def user_loader(user_id):
print('user loader', type(user_id), user_id)
return User.get(user_id)
Problem is that, same setup is working in my local ubuntu environment but when i moved to production vm on centos, it works for one view where i am using local authentication but not in case of okta.
user_loader is how you obtain the User object. You give it an id and it gives you back the User. See the docs.
In your case you already have the User object so login_user(user) has no need to call the user_loader.
user_loader is likely called wherever you do something like user = ....
Not sure about the issue but i changed the flask app folder structure. changed the way i was using blueprints and issue got solved. All in all, had to change complete application structure.

Cloud Composer non interactive authentication

I've tried a lot before posting this question, I'm not against down votes, at least let me know WHY you are down voting.
I have built an Airflow plugin to fetch data from Cloud composer Airflow environment and accessing the cloud composer is working great from browsers as it needs the user to sign in before accessing any of the Airflow endpoints.
In my use case, I need to trigger the endpoints via code. Is there a way in which I can do this.
Below is the Airflow-Flask plugin being used
from datetime import datetime
import json
import os
import six
import time
from flask import Blueprint, request, Response
from sqlalchemy import or_
from airflow import settings
from airflow.exceptions import AirflowException, AirflowConfigException
from airflow.models import DagBag, DagRun
from airflow.utils.state import State
from airflow.utils.dates import date_range as utils_date_range
from airflow.www.app import csrf
airflow_api_blueprint = Blueprint('airflow_api', __name__, url_prefix='/api/v1')
class ApiInputException(Exception):
pass
class ApiResponse:
def __init__(self):
pass
STATUS_OK = 200
STATUS_BAD_REQUEST = 400
STATUS_UNAUTHORIZED = 401
STATUS_NOT_FOUND = 404
STATUS_SERVER_ERROR = 500
#staticmethod
def standard_response(status, payload):
json_data = json.dumps({
'response': payload
})
resp = Response(json_data, status=status, mimetype='application/json')
return resp
#staticmethod
def success(payload):
return ApiResponse.standard_response(ApiResponse.STATUS_OK, payload)
#staticmethod
def error(status, error):
return ApiResponse.standard_response(status, {
'error': error
})
#staticmethod
def bad_request(error):
return ApiResponse.error(ApiResponse.STATUS_BAD_REQUEST, error)
#staticmethod
def not_found(error='Resource not found'):
return ApiResponse.error(ApiResponse.STATUS_NOT_FOUND, error)
#staticmethod
def unauthorized(error='Not authorized to access this resource'):
return ApiResponse.error(ApiResponse.STATUS_UNAUTHORIZED, error)
#staticmethod
def server_error(error='An unexpected problem occurred'):
return ApiResponse.error(ApiResponse.STATUS_SERVER_ERROR, error)
#airflow_api_blueprint.before_request
def verify_authentication():
authorization = request.headers.get('authorization')
try:
api_auth_key = settings.conf.get('AIRFLOW_API_PLUGIN', 'AIRFLOW_API_AUTH')
except AirflowConfigException:
return
if authorization != api_auth_key:
return ApiResponse.unauthorized("You are not authorized to use this resource")
def format_dag_run(dag_run):
return {
'run_id': dag_run.run_id,
'dag_id': dag_run.dag_id,
'state': dag_run.get_state(),
'start_date': (None if not dag_run.start_date else str(dag_run.start_date)),
'end_date': (None if not dag_run.end_date else str(dag_run.end_date)),
'external_trigger': dag_run.external_trigger,
'execution_date': str(dag_run.execution_date)
}
def find_dag_runs(session, dag_id, dag_run_id, execution_date):
qry = session.query(DagRun)
qry = qry.filter(DagRun.dag_id == dag_id)
qry = qry.filter(or_(DagRun.run_id == dag_run_id, DagRun.execution_date == execution_date))
return qry.order_by(DagRun.execution_date).all()
#airflow_api_blueprint.route('/dags', methods=['GET'])
def dags_index():
dagbag = DagBag()
dags = []
for dag_id in dagbag.dags:
payload = {
'dag_id': dag_id,
'full_path': None,
'is_active': False,
'last_execution': None,
}
dag = dagbag.get_dag(dag_id)
if dag:
payload['full_path'] = dag.full_filepath
payload['is_active'] = (not dag.is_paused)
payload['last_execution'] = str(dag.latest_execution_date)
if request.args.get('dag_id') is not None:
if request.args.get('dag_id') not in payload['dag_id']:
continue
dags.append(payload)
return ApiResponse.success({'dags': dags})
#airflow_api_blueprint.route('/dag_runs', methods=['GET'])
def get_dag_runs():
dag_runs = []
session = settings.Session()
query = session.query(DagRun)
if request.args.get('state') is not None:
query = query.filter(DagRun.state == request.args.get('state'))
if request.args.get('external_trigger') is not None:
# query = query.filter(DagRun.external_trigger == (request.args.get('external_trigger') is True))
query = query.filter(DagRun.external_trigger == (request.args.get('external_trigger') in ['true', 'True']))
if request.args.get('prefix') is not None:
query = query.filter(DagRun.run_id.ilike('{}%'.format(request.args.get('prefix'))))
if request.args.get('dag_id') is not None:
query = query.filter(DagRun.dag_id.ilike('{}%'.format(request.args.get('dag_id'))))
runs = query.order_by(DagRun.execution_date).all()
for run in runs:
dag_runs.append(format_dag_run(run))
session.close()
return ApiResponse.success({'dag_runs': dag_runs})
#csrf.exempt
#airflow_api_blueprint.route('/dag_runs', methods=['POST'])
def create_dag_run():
# decode input
data = request.get_json(force=True)
# ensure there is a dag id
if 'dag_id' not in data or data['dag_id'] is None:
return ApiResponse.bad_request('Must specify the dag id to create dag runs for')
dag_id = data['dag_id']
limit = 500
partial = False
if 'limit' in data and data['limit'] is not None:
try:
limit = int(data['limit'])
if limit <= 0:
return ApiResponse.bad_request('Limit must be a number greater than 0')
if limit > 500:
return ApiResponse.bad_request('Limit cannot exceed 500')
except ValueError:
return ApiResponse.bad_request('Limit must be an integer')
if 'partial' in data and data['partial'] in ['true', 'True', True]:
partial = True
# ensure there is run data
start_date = datetime.now()
end_date = datetime.now()
if 'start_date' in data and data['start_date'] is not None:
try:
start_date = datetime.strptime(data['start_date'], '%Y-%m-%dT%H:%M:%S')
except ValueError:
error = '\'start_date\' has invalid format \'{}\', Ex format: YYYY-MM-DDThh:mm:ss'
return ApiResponse.bad_request(error.format(data['start_date']))
if 'end_date' in data and data['end_date'] is not None:
try:
end_date = datetime.strptime(data['end_date'], '%Y-%m-%dT%H:%M:%S')
except ValueError:
error = '\'end_date\' has invalid format \'{}\', Ex format: YYYY-MM-DDThh:mm:ss'
return ApiResponse.bad_request(error.format(data['end_date']))
# determine run_id prefix
prefix = 'manual_{}'.format(int(time.time()))
if 'prefix' in data and data['prefix'] is not None:
prefix = data['prefix']
if 'backfill' in prefix:
return ApiResponse.bad_request('Prefix cannot contain \'backfill\', Airflow will ignore dag runs using it')
# ensure prefix doesn't have an underscore appended
if prefix[:-1:] == "_":
prefix = prefix[:-1]
conf = None
if 'conf' in data and data['conf'] is not None:
if isinstance(data['conf'], six.string_types):
conf = data['conf']
else:
try:
conf = json.dumps(data['conf'])
except Exception:
return ApiResponse.bad_request('Could not encode specified conf JSON')
try:
session = settings.Session()
dagbag = DagBag('dags')
if dag_id not in dagbag.dags:
return ApiResponse.bad_request("Dag id {} not found".format(dag_id))
dag = dagbag.get_dag(dag_id)
# ensure run data has all required attributes and that everything is valid, returns transformed data
runs = utils_date_range(start_date=start_date, end_date=end_date, delta=dag._schedule_interval)
if len(runs) > limit and partial is False:
error = '{} dag runs would be created, which exceeds the limit of {}.' \
' Reduce start/end date to reduce the dag run count'
return ApiResponse.bad_request(error.format(len(runs), limit))
payloads = []
for exec_date in runs:
run_id = '{}_{}'.format(prefix, exec_date.isoformat())
if find_dag_runs(session, dag_id, run_id, exec_date):
continue
payloads.append({
'run_id': run_id,
'execution_date': exec_date,
'conf': conf
})
results = []
for index, run in enumerate(payloads):
if len(results) >= limit:
break
dag.create_dagrun(
run_id=run['run_id'],
execution_date=run['execution_date'],
state=State.RUNNING,
conf=conf,
external_trigger=True
)
results.append(run['run_id'])
session.close()
except ApiInputException as e:
return ApiResponse.bad_request(str(e))
except ValueError as e:
return ApiResponse.server_error(str(e))
except AirflowException as e:
return ApiResponse.server_error(str(e))
except Exception as e:
return ApiResponse.server_error(str(e))
return ApiResponse.success({'dag_run_ids': results})
#airflow_api_blueprint.route('/dag_runs/<dag_run_id>', methods=['GET'])
def get_dag_run(dag_run_id):
session = settings.Session()
runs = DagRun.find(run_id=dag_run_id, session=session)
if len(runs) == 0:
return ApiResponse.not_found('Dag run not found')
dag_run = runs[0]
session.close()
return ApiResponse.success({'dag_run': format_dag_run(dag_run)})
For programmatic auth to Composer's Airflow webserver, you will have to authenticate through IAP.
https://cloud.google.com/composer/docs/how-to/using/triggering-with-gcf contains a js example of this.
https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/iap/make_iap_request.py is a Python example, but note that it is missing a timeout on the final IAP request (https://github.com/GoogleCloudPlatform/python-docs-samples/issues/1812).

Creating a Superset user programmatically

How to go about creating users programmatically in Superset with a defined role?
Say I have an email sign in page. User signs up. After sign up I would like to programmatically create the user with the gamma role into a Superset instance.
I've used a reply from superset git and created the following classes to add users with roles.
You need to have a json file with the users info and the name of the role(s) that you want to apply. Like this:
[
{
"first_name": "a",
"last_name": "b",
"username": "abc",
"email": "abc#def.com",
"password": "123",
"roles": ["generalViewer","databaseXView"]
},
{
"first_name": "b",
"last_name": "c",
"username": "bcd",
"email": "bcd#def.com",
"password": "123",
"roles": ["generalViewer","databaseXView"]
}
]
Because we are using the role name (we can get them from the superset through normal navigation) is needed to setup the database credentials to get the role_id. Also need to setup the login of any user with permissions to create another one.
import requests
import json
from bs4 import BeautifulSoup as bs
from bs4 import Comment
from time import sleep
import psycopg2
from typing import List, Tuple
import pandas as pd
class SupersetApi:
def __init__(self, username=None, password=None):
self.s = requests.session()
self.base_url = "http://123.45.67.890:8088/" #superset server ip
self._csrf = self._getCSRF(self.url('login/'))
self.headers = {'X-CSRFToken': self._csrf, 'Referer': self.url('login/')}
# note: does not use headers because of flask_wtf.csrf.validate_csrf
# if data is dict it is used as form and ends up empty but flask_wtf checks if data ...
payload = {'username': username, 'password': password, 'csrf_token': self._csrf}
self.post('login/', payload, None)
#self.s.post(self.url(), data=payload, headers = {})
def url(self, url_path):
return self.base_url + url_path
def get(self, url_path):
return self.s.get(self.url(url_path), headers=self.headers)
def post(self, url_path, data=None, json_data=None, **kwargs):
kwargs.update({'url': self.url(url_path), 'headers': self.headers})
if data:
data['csrf_token'] = self._csrf
kwargs['data'] = data
if json_data:
kwargs['json'] = json_data
try:
response = self.s.post(**kwargs)
statusCode = response.status_code
if statusCode < 399:
return True, statusCode
else:
print('POST response: {}'.format(statusCode))
return False, statusCode
except Exception as e:
print(e)
return False, e
def _getCSRF(self, url_path):
try:
response = self.s.get(url_path)
response.raise_for_status()
except Exception as e:
print(e)
exit()
soup = bs(response.content, "html.parser")
for tag in soup.find_all('input', id='csrf_token'):
csrf_token = tag['value']
return csrf_token
class PostgresDB:
def __init__(self, db_name: str, db_user: str, db_host: str, db_password: str) -> None:
self.db_name = db_name
self.db_user = db_user
self.db_host = db_host
self.db_password = db_password
self.conn = None
self.cur = None
def connect(self) -> None:
try:
self.conn = psycopg2.connect('dbname={} user={} host={} password={}'.format(
self.db_name, self.db_user, self.db_host, self.db_password))
self.cur = self.conn.cursor()
except Exception as e:
raise Exception('Couldn\'t connect to the database. Error: {}'.format(e))
def commit(self) -> None:
if self.conn is not None:
self.conn.commit()
else:
raise Exception('Connection not opened to commit')
def close_connection(self) -> None:
if self.cur is not None or self.conn is not None:
try:
self.cur.close()
except:
pass
try:
self.conn.close()
except:
pass
else:
print('Connection and Cursor not opened to be closed')
def get_from_ab_role(self, columns: List, filters: str) -> List:
roles_table = 'public.ab_role'
sql_columns = ','.join(columns)
sql = "SELECT {} FROM {} WHERE {}".format(sql_columns, roles_table, filters)
self.cur.execute(sql)
return self.cur.fetchall()
class SupersetUser:
def __init__(self, user_dict: dict, db: 'PostgresDB'):
self.first_name = user_dict.get('first_name')
self.last_name = user_dict.get('last_name')
self.username = user_dict.get('username')
self.email = user_dict.get('email')
self.active = True
self.password = user_dict.get('password')
self.roles = self.get_role_id(db, user_dict.get('roles'))
def get_role_id(self, db: 'PostgresDB', roles: List):
filter = 'name = '
filter = filter + "'{}' ".format(roles[0])
if len(roles) > 1:
for role in roles[1:]:
filter = filter + "OR name = '{}' ".format(role)
ids = []
for id in db.get_from_ab_role(['id'], filter):
ids.append(str(id[0]))
return ids
def create_user(self, superset_api: SupersetApi) -> Tuple:
Error_friendly_message = None
Error_not = True
url_path = 'users/api/create'
payload = {'first_name': self.first_name,
'last_name': self.last_name,
'username': self.username,
'email': self.email,
'active': self.active,
'conf_password': self.password,
'password': self.password,
'roles': self.roles
}
Error_not, http_response_code = superset_api.post(url_path=url_path, json=payload)
if Error_not:
print('User {} created. Corresponding e-mail: {}'.format(self.username, self.email))
return Error_not, Error_friendly_message, http_response_code
elif http_response_code == 500:
Error_not = False
Error_friendly_message = ('Ops! Something went wrong. Probably already exist an '
'user with the same e-mail: {}, or an error with the json variables... '
'All of them must be strings or a list of strings'.format(self.email))
return Error_not, Error_friendly_message, http_response_code
else:
Error_not = False
Error_friendly_message = 'Ops! Something went wrong. Try again.'
return Error_not, Error_friendly_message, http_response_code
#file that contains the users to be created
FILE_NAME = 'users.json'
#need credentials from user with admin role to create new user
ADMIN_USR = 'admin'
ADMIN_PSWD = 'adminpassword'
DB_NAME = 'superset_database'
DB_USER = 'superset_user'
DB_HOST = '123.45.67.890'
DB_PSWD = 'superset_password'
superset = SupersetApi(ADMIN_USR, ADMIN_PSWD)
portgre_db = PostgresDB(DB_NAME, DB_USER, DB_HOST, DB_PSWD)
portgre_db.connect()
try:
with open(FILE_NAME, 'r') as f:
users = json.load(f)
print('File successfully read')
except FileNotFoundError as e:
print(e)
for index, user in enumerate(users):
userRoles = []
superset_user = SupersetUser(user, portgre_db)
Error_not, Error_friendly_message, http_response_code = superset_user.create_user(superset)
if not Error_not:
print('Could\'t create user {}.'.format(superset_user.username))
print(Error_friendly_message)
print('HTTP Response Code: {}'.format(http_response_code))
portgre_db.close_connection()
If you're set on creating users programmatically, then your best bet is probably to look into overriding the SecurityManager of the underlying Flask-App-Builder (F.A.B.) framework - which Superset is based off.
The documentation can be found here: https://github.com/dpgaspar/Flask-AppBuilder/blob/master/docs/security.rst#your-custom-security
That being said - it feels your question is really about how to allow users to self-register in Superset and give them the Gamma role by default.
This is a use-case FAB addresses explicitly, and you should be able to achieve this just through configuration: http://flask-appbuilder.readthedocs.io/en/latest/user_registration.html

rauth/flask: How to login via Twitter?

The provided example in rauth is using the PIN instead of the callback. I don't understand how this should work via web callback.
1) Minor problem:
According to twitter, if oauth_callback URL is passed in, then it should be used instead whatever entry is in the https://dev.twitter.com settings. However this doesn't seem to be true, if I dont set it to http://127.0.0.1:8080/twitter/authorized it would never get to that Url after a successful authorization.
app.add_url_rule('/twitter/login', view_func=views.twitter_login)
app.add_url_rule('/twitter/authorized', 'twitter_authorized', view_func=views.twitter_authorized)
def twitter_login():
request_token, request_token_secret = twitter.get_request_token()
redirect_uri = url_for('twitter_authorized', _external=True)
params = {'oauth_callback': redirect_uri, 'request_token':request_token}
return redirect(twitter.get_authorize_url(**params))
2) Major problem is here:
I can see the request.args has both ['oauth_token'] and ['oauth_verifier'].
But I don't understand how to use them to get the twitter session for obtaining user details such as picture and display name:
def twitter_authorized():
tw_session = twitter.get_auth_session(request_token ??? , request_token_secret ???)
resp = tw_session.get("account/verify_credentials.json", params={'format':'json'})
me = resp.json()
user = User.get_or_create(...)
if user:
login_user(user)
return redirect(url_for('index'))
If someone could shed some light on this, would be highly appreciated.
Here's a working Twitter sign-in examples using Flask based on the Facebook example:
'''
twitter
-------
A simple Flask demo app that shows how to login with Twitter via rauth.
Please note: you must do `from twitter import db; db.create_all()` from
the interpreter before running this example!
'''
from flask import (Flask, flash, request, redirect, render_template, session,
url_for)
from flask.ext.sqlalchemy import SQLAlchemy
from rauth.service import OAuth1Service
from rauth.utils import parse_utf8_qsl
# Flask config
SQLALCHEMY_DATABASE_URI = 'sqlite:///twitter.db'
SECRET_KEY = '\xfb\x12\xdf\xa1#i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16'
DEBUG = True
TW_KEY = 'oZSbVzKCeyAZTDxw1RKog'
TW_SECRET = 'TuNoqA6NzEBS3Zrb8test7bxQfKTlBfLTXsZ8RaKAo'
# Flask setup
app = Flask(__name__)
app.config.from_object(__name__)
db = SQLAlchemy(app)
# rauth OAuth 1.0 service wrapper
twitter = OAuth1Service(
name='twitter',
consumer_key=TW_KEY,
consumer_secret=TW_SECRET,
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authorize',
base_url='https://api.twitter.com/1.1/')
# models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
fb_id = db.Column(db.String(120))
def __init__(self, username, fb_id):
self.username = username
self.fb_id = fb_id
def __repr__(self):
return '<User %r>' % self.username
#staticmethod
def get_or_create(username, fb_id):
user = User.query.filter_by(username=username).first()
if user is None:
user = User(username, fb_id)
db.session.add(user)
db.session.commit()
return user
# views
#app.route('/')
def index():
return render_template('login.html')
#app.route('/twitter/login')
def login():
oauth_callback = url_for('authorized', _external=True)
params = {'oauth_callback': oauth_callback}
r = twitter.get_raw_request_token(params=params)
data = parse_utf8_qsl(r.content)
session['twitter_oauth'] = (data['oauth_token'],
data['oauth_token_secret'])
return redirect(twitter.get_authorize_url(data['oauth_token'], **params))
#app.route('/twitter/authorized')
def authorized():
request_token, request_token_secret = session.pop('twitter_oauth')
# check to make sure the user authorized the request
if not 'oauth_token' in request.args:
flash('You did not authorize the request')
return redirect(url_for('index'))
try:
creds = {'request_token': request_token,
'request_token_secret': request_token_secret}
params = {'oauth_verifier': request.args['oauth_verifier']}
sess = twitter.get_auth_session(params=params, **creds)
except Exception, e:
flash('There was a problem logging into Twitter: ' + str(e))
return redirect(url_for('index'))
verify = sess.get('account/verify_credentials.json',
params={'format':'json'}).json()
User.get_or_create(verify['screen_name'], verify['id'])
flash('Logged in as ' + verify['name'])
return redirect(url_for('index'))
if __name__ == '__main__':
db.create_all()
app.run()
Hope that helps!

How to make facebook app not ask for permission to use friends

Hi I use python for my facebook app and want to restrict my app from gaining permissions to the users friends. How can I accomplish this? Some code is conf.py
# Facebook Application ID and Secret.
FACEBOOK_APP_ID = '103297833078853'
FACEBOOK_APP_SECRET = 'd1f7a3dfeb650b6826456a5660134f58'
# Canvas Page name.
FACEBOOK_CANVAS_NAME = 'cyberfaze'
# A random token for use with the Real-time API.
FACEBOOK_REALTIME_VERIFY_TOKEN = 'RANDOM TOKEN'
# The external URL this application is available at where the Real-time API will
# send it's pings.
EXTERNAL_HREF = 'http://cyberfaze.appspot.com/'
# Facebook User IDs of admins. The poor mans admin system.
ADMIN_USER_IDS = ['5526183']
Here is main.py
#!/usr/bin/env python
# coding: utf-8
# Copyright 2011 Facebook, 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.
import os
# dummy config to enable registering django template filters
os.environ[u'DJANGO_SETTINGS_MODULE'] = u'conf'
from google.appengine.dist import use_library
use_library('django', '1.2')
from django.template.defaultfilters import register
from django.utils import simplejson as json
from functools import wraps
from google.appengine.api import urlfetch, taskqueue
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp import util, template
from google.appengine.runtime import DeadlineExceededError
from random import randrange
from uuid import uuid4
import Cookie
import base64
import cgi
import conf
import datetime
import hashlib
import hmac
import logging
import time
import traceback
import urllib
def htmlescape(text):
"""Escape text for use as HTML"""
return cgi.escape(
text, True).replace("'", ''').encode('ascii', 'xmlcharrefreplace')
#register.filter(name=u'get_name')
def get_name(dic, index):
"""Django template filter to render name"""
return dic[index].name
#register.filter(name=u'get_picture')
def get_picture(dic, index):
"""Django template filter to render picture"""
return dic[index].picture
def select_random(lst, limit):
"""Select a limited set of random non Falsy values from a list"""
final = []
size = len(lst)
while limit and size:
index = randrange(min(limit, size))
size = size - 1
elem = lst[index]
lst[index] = lst[size]
if elem:
limit = limit - 1
final.append(elem)
return final
_USER_FIELDS = u'name,email,picture,friends'
class User(db.Model):
user_id = db.StringProperty(required=True)
access_token = db.StringProperty(required=True)
name = db.StringProperty(required=True)
picture = db.StringProperty(required=True)
email = db.StringProperty()
friends = db.StringListProperty()
dirty = db.BooleanProperty()
def refresh_data(self):
"""Refresh this user's data using the Facebook Graph API"""
me = Facebook().api(u'/me',
{u'fields': _USER_FIELDS, u'access_token': self.access_token})
self.dirty = False
self.name = me[u'name']
self.email = me.get(u'email')
self.picture = me[u'picture']
self.friends = [user[u'id'] for user in me[u'friends'][u'data']]
return self.put()
class Run(db.Model):
user_id = db.StringProperty(required=True)
location = db.StringProperty(required=True)
distance = db.FloatProperty(required=True)
date = db.DateProperty(required=True)
#staticmethod
def find_by_user_ids(user_ids, limit=50):
if user_ids:
return Run.gql(u'WHERE user_id IN :1', user_ids).fetch(limit)
else:
return []
#property
def pretty_distance(self):
return u'%.2f' % self.distance
class RunException(Exception):
pass
class FacebookApiError(Exception):
def __init__(self, result):
self.result = result
def __str__(self):
return self.__class__.__name__ + ': ' + json.dumps(self.result)
class Facebook(object):
"""Wraps the Facebook specific logic"""
def __init__(self, app_id=conf.FACEBOOK_APP_ID,
app_secret=conf.FACEBOOK_APP_SECRET):
self.app_id = app_id
self.app_secret = app_secret
self.user_id = None
self.access_token = None
self.signed_request = {}
def api(self, path, params=None, method=u'GET', domain=u'graph'):
"""Make API calls"""
if not params:
params = {}
params[u'method'] = method
if u'access_token' not in params and self.access_token:
params[u'access_token'] = self.access_token
result = json.loads(urlfetch.fetch(
url=u'https://' + domain + u'.facebook.com' + path,
payload=urllib.urlencode(params),
method=urlfetch.POST,
headers={
u'Content-Type': u'application/x-www-form-urlencoded'})
.content)
if isinstance(result, dict) and u'error' in result:
raise FacebookApiError(result)
return result
def load_signed_request(self, signed_request):
"""Load the user state from a signed_request value"""
try:
sig, payload = signed_request.split(u'.', 1)
sig = self.base64_url_decode(sig)
data = json.loads(self.base64_url_decode(payload))
expected_sig = hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()
# allow the signed_request to function for upto 1 day
if sig == expected_sig and \
data[u'issued_at'] > (time.time() - 86400):
self.signed_request = data
self.user_id = data.get(u'user_id')
self.access_token = data.get(u'oauth_token')
except ValueError, ex:
pass # ignore if can't split on dot
#property
def user_cookie(self):
"""Generate a signed_request value based on current state"""
if not self.user_id:
return
payload = self.base64_url_encode(json.dumps({
u'user_id': self.user_id,
u'issued_at': str(int(time.time())),
}))
sig = self.base64_url_encode(hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest())
return sig + '.' + payload
#staticmethod
def base64_url_decode(data):
data = data.encode(u'ascii')
data += '=' * (4 - (len(data) % 4))
return base64.urlsafe_b64decode(data)
#staticmethod
def base64_url_encode(data):
return base64.urlsafe_b64encode(data).rstrip('=')
class CsrfException(Exception):
pass
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
csrf_protect = True
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
self.init_csrf()
self.response.headers[u'P3P'] = u'CP=HONK' # iframe cookies in IE
except Exception, ex:
self.log_exception(ex)
raise
def handle_exception(self, ex, debug_mode):
"""Invoked for unhandled exceptions by webapp"""
self.log_exception(ex)
self.render(u'error',
trace=traceback.format_exc(), debug_mode=debug_mode)
def log_exception(self, ex):
"""Internal logging handler to reduce some App Engine noise in errors"""
msg = ((str(ex) or ex.__class__.__name__) +
u': \n' + traceback.format_exc())
if isinstance(ex, urlfetch.DownloadError) or \
isinstance(ex, DeadlineExceededError) or \
isinstance(ex, CsrfException) or \
isinstance(ex, taskqueue.TransientError):
logging.warn(msg)
else:
logging.error(msg)
def set_cookie(self, name, value, expires=None):
"""Set a cookie"""
if value is None:
value = 'deleted'
expires = datetime.timedelta(minutes=-50000)
jar = Cookie.SimpleCookie()
jar[name] = value
jar[name]['path'] = u'/'
if expires:
if isinstance(expires, datetime.timedelta):
expires = datetime.datetime.now() + expires
if isinstance(expires, datetime.datetime):
expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
jar[name]['expires'] = expires
self.response.headers.add_header(*jar.output().split(u': ', 1))
def render(self, name, **data):
"""Render a template"""
if not data:
data = {}
data[u'js_conf'] = json.dumps({
u'appId': conf.FACEBOOK_APP_ID,
u'canvasName': conf.FACEBOOK_CANVAS_NAME,
u'userIdOnServer': self.user.user_id if self.user else None,
})
data[u'logged_in_user'] = self.user
data[u'message'] = self.get_message()
data[u'csrf_token'] = self.csrf_token
data[u'canvas_name'] = conf.FACEBOOK_CANVAS_NAME
self.response.out.write(template.render(
os.path.join(
os.path.dirname(__file__), 'templates', name + '.html'),
data))
def init_facebook(self):
"""Sets up the request specific Facebook and User instance"""
facebook = Facebook()
user = None
# initial facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# we reset the method to GET because a request from facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. in webapp causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# refresh data if we failed in doing so after a realtime ping
if user.dirty:
user.refresh_data()
# restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # ignore if can't get the minimum fields
self.facebook = facebook
self.user = user
def init_csrf(self):
"""Issue and handle CSRF token as necessary"""
self.csrf_token = self.request.cookies.get(u'c')
if not self.csrf_token:
self.csrf_token = str(uuid4())[:8]
self.set_cookie('c', self.csrf_token)
if self.request.method == u'POST' and self.csrf_protect and \
self.csrf_token != self.request.POST.get(u'_csrf_token'):
raise CsrfException(u'Missing or invalid CSRF token.')
def set_message(self, **obj):
"""Simple message support"""
self.set_cookie('m', base64.b64encode(json.dumps(obj)) if obj else None)
def get_message(self):
"""Get and clear the current message"""
message = self.request.cookies.get(u'm')
if message:
self.set_message() # clear the current cookie
return json.loads(base64.b64decode(message))
def user_required(fn):
"""Decorator to ensure a user is present"""
#wraps(fn)
def wrapper(*args, **kwargs):
handler = args[0]
if handler.user:
return fn(*args, **kwargs)
handler.redirect(u'/')
return wrapper
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
class UserRunsHandler(BaseHandler):
"""Show a specific user's runs, ensure friendship with the logged in user"""
#user_required
def get(self, user_id):
if self.user.friends.count(user_id) or self.user.user_id == user_id:
user = User.get_by_key_name(user_id)
if not user:
self.set_message(type=u'error',
content=u'That user does not use Run with Friends.')
self.redirect(u'/')
return
self.render(u'user',
user=user,
runs=Run.find_by_user_ids([user_id]),
)
else:
self.set_message(type=u'error',
content=u'You are not allowed to see that.')
self.redirect(u'/')
class RunHandler(BaseHandler):
"""Add a run"""
#user_required
def post(self):
try:
location = self.request.POST[u'location'].strip()
if not location:
raise RunException(u'Please specify a location.')
distance = float(self.request.POST[u'distance'].strip())
if distance < 0:
raise RunException(u'Invalid distance.')
date_year = int(self.request.POST[u'date_year'].strip())
date_month = int(self.request.POST[u'date_month'].strip())
date_day = int(self.request.POST[u'date_day'].strip())
if date_year < 0 or date_month < 0 or date_day < 0:
raise RunException(u'Invalid date.')
date = datetime.date(date_year, date_month, date_day)
run = Run(
user_id=self.user.user_id,
location=location,
distance=distance,
date=date,
)
run.put()
title = run.pretty_distance + u' miles #' + location
publish = u'<a onclick=\'publishRun(' + \
json.dumps(htmlescape(title)) + u')\'>Post to facebook.</a>'
self.set_message(type=u'success',
content=u'Added your run. ' + publish)
except RunException, e:
self.set_message(type=u'error', content=unicode(e))
except KeyError:
self.set_message(type=u'error',
content=u'Please specify location, distance & date.')
except ValueError:
self.set_message(type=u'error',
content=u'Please specify a valid distance & date.')
except Exception, e:
self.set_message(type=u'error',
content=u'Unknown error occured. (' + unicode(e) + u')')
self.redirect(u'/')
class RealtimeHandler(BaseHandler):
"""Handles Facebook Real-time API interactions"""
csrf_protect = False
def get(self):
if (self.request.GET.get(u'setup') == u'1' and
self.user and conf.ADMIN_USER_IDS.count(self.user.user_id)):
self.setup_subscription()
self.set_message(type=u'success',
content=u'Successfully setup Real-time subscription.')
elif (self.request.GET.get(u'hub.mode') == u'subscribe' and
self.request.GET.get(u'hub.verify_token') ==
conf.FACEBOOK_REALTIME_VERIFY_TOKEN):
self.response.out.write(self.request.GET.get(u'hub.challenge'))
logging.info(
u'Successful Real-time subscription confirmation ping.')
return
else:
self.set_message(type=u'error',
content=u'You are not allowed to do that.')
self.redirect(u'/')
def post(self):
body = self.request.body
if self.request.headers[u'X-Hub-Signature'] != (u'sha1=' + hmac.new(
self.facebook.app_secret,
msg=body,
digestmod=hashlib.sha1).hexdigest()):
logging.error(
u'Real-time signature check failed: ' + unicode(self.request))
return
data = json.loads(body)
if data[u'object'] == u'user':
for entry in data[u'entry']:
taskqueue.add(url=u'/task/refresh-user/' + entry[u'id'])
logging.info('Added task to queue to refresh user data.')
else:
logging.warn(u'Unhandled Real-time ping: ' + body)
def setup_subscription(self):
path = u'/' + conf.FACEBOOK_APP_ID + u'/subscriptions'
params = {
u'access_token': conf.FACEBOOK_APP_ID + u'|' +
conf.FACEBOOK_APP_SECRET,
u'object': u'user',
u'fields': _USER_FIELDS,
u'callback_url': conf.EXTERNAL_HREF + u'realtime',
u'verify_token': conf.FACEBOOK_REALTIME_VERIFY_TOKEN,
}
response = self.facebook.api(path, params, u'POST')
logging.info(u'Real-time setup API call response: ' + unicode(response))
class RefreshUserHandler(BaseHandler):
"""Used as an App Engine Task to refresh a single user's data if possible"""
csrf_protect = False
def post(self, user_id):
logging.info('Refreshing user data for ' + user_id)
user = User.get_by_key_name(user_id)
if not user:
return
try:
user.refresh_data()
except FacebookApiError:
user.dirty = True
user.put()
def main():
routes = [
(r'/', RecentRunsHandler),
(r'/user/(.*)', UserRunsHandler),
(r'/run', RunHandler),
(r'/realtime', RealtimeHandler),
(r'/task/refresh-user/(.*)', RefreshUserHandler),
]
application = webapp.WSGIApplication(routes,
debug=os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'))
util.run_wsgi_app(application)
if __name__ == u'__main__':
main()
I think, and I'm still working with this example project myself, that if you modify this line AND strip out any "friend" references then it should work:
_USER_FIELDS = u'name,email,picture,friends'
becomes
_USER_FIELDS = u'name,email,picture'
It will be a lot of work to strip out all the "friend" references, but from what I can tell that's the only string that the app uses to request user information from the graph api.