def send(template_data={}, **kwargs): # Determine if message should send if util.is_development() and not config.should_deliver_smtp_dev: logging.info('\n\n---------------------------------------------------') logging.info('Email not sent, config.should_deliver_smtp_dev False.') logging.info('Email params:') logging.info(kwargs) logging.info(template_data) if kwargs['template'] == 'authorship_email.html': logging.info( '/{entity_type}/{entity_id}/accept_authorship?uid={to_uid}&token={token}' .format(**template_data) ) logging.info('\n---------------------------------------------------\n') return subject = render(kwargs['subject'], **template_data) # Determine if using html string or a template body = '' if 'body' in kwargs: body = render(kwargs['body'], **template_data) elif 'template' in kwargs: body = render_template(kwargs['template'], **template_data) # JSON for Mandrill HTTP POST request sv = ndb.Key('SecretValue', 'mandrill_api_key').get() api_key = getattr(sv, 'value', config.mandrill_api_key) if not util.is_development() and api_key is config.mandrill_api_key: logging.error("No mandrill api_key set in production!") json_mandrill = { "key": api_key, "message": { "html": body, "subject": subject, "from_email": config.from_server_email_address, "from_name": "BELE Library", "inline_css": True, "to": format_to_address(kwargs['to_address']) } } # URL for Mandrill HTTP POST request url = "https://mandrillapp.com/api/1.0/messages/send.json" rpc = urlfetch.create_rpc() urlfetch.make_fetch_call(rpc, url=url, payload=json.dumps(json_mandrill), method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'}) try: result = rpc.get_result() logging.info(u"...{}".format(result.status_code)) logging.info(result.content) if result.status_code == 200: text = result.content except urlfetch.DownloadError: # Request timed out or failed. logging.error('Email failed to send.') result = None return result
def delete_everything(self): if self.user.user_type == 'god' and util.is_development(): util.delete_everything() else: raise PermissionDenied("Only gods working on a development server " "can delete everything.") return True
def mandrill_send(template_data={}, **kwargs): # Determine if message should send if util.is_development() and not config.should_deliver_smtp_dev: logging.info('Email not sent, check config!') return None subject = render(kwargs['subject'], **template_data) # Add in default template data template_data['to_address'] = kwargs.get('to_address', None) template_data['domain'] = util.get_domain() # Python keeps time to the microsecond, but we don't need it, and # it's easier to render as ISO 8601 without it. template_data['server_time'] = datetime.datetime.today().replace(microsecond=0) template_data['contact_email_address'] = config.from_server_email_address # Determine if using html string or a template html_body = None if 'html' in kwargs: html_body = kwargs['html'] elif 'body' in kwargs: html_body = render(kwargs['body'], **template_data) elif 'template' in kwargs: html_body = render_template(kwargs['template'], **template_data) text_body = kwargs.get('text', None) if util.is_localhost() or util.is_testing(): sender = _send_localhost_and_testing elif util.is_development(): sender = _send_development else: sender = _send_production optional_mandrill_keys = ('from_address', 'reply_to', 'from_name') optional_mandrill_kwargs = {k: kwargs[k] for k in optional_mandrill_keys if k in kwargs} return sender(kwargs['to_address'], subject, html_body, text_body, kwargs.get('mandrill_template', None), kwargs.get('mandrill_template_content', None), kwargs.get('cc_address', None), kwargs.get('bcc_address', None), **optional_mandrill_kwargs)
def get_public_rsa(): """Get key for verifying asymmetric token. Defaults to config.""" sv_entity = SecretValue.get_by_id('jwt_public_rsa') if sv_entity is None: if not util.is_development(): logging.error("No default jwt rsa public key set in production!") return config.default_jwt_public_rsa else: return sv_entity.value
def get_secret(): """Get secret for signing/verifying symmetric token. Defaults to config.""" sv_entity = SecretValue.get_by_id('jwt_secret') if sv_entity is None: if not util.is_development(): logging.error("No default jwt secret set in production!") return config.default_jwt_secret else: return sv_entity.value
def reset(self, table_definitions): """Drop all given tables and re-created them. Takes a dictionary of table name to with CREATE TABLE query string. """ if not util.is_development(): raise Exception("You REALLY don't want to do that.") for table, definition in table_definitions.items(): self.query('DROP TABLE IF EXISTS `{}`;'.format(table)) self.query(definition)
def send(template_data={}, **kwargs): subject = render(kwargs['subject'], **template_data) # Determine if using html string or a template body = '' if 'body' in kwargs: body = render(kwargs['body'], **template_data) elif 'template' in kwargs: body = render_template(kwargs['template'], **template_data) # JSON for Mandrill HTTP POST request json_mandrill = { # "key": this is added later, to make sure it's not logged. "message": { "html": body, "subject": subject, "from_email": config.from_yellowstone_email_address, "from_name": "PERTS", "inline_css": True, "to": format_to_address(kwargs['to_address']) } } # Determine if message should send if util.is_development() and not config.should_deliver_smtp_dev: logging.info('Email not sent, check config!') logging.info(json_mandrill) return None api_key = SecretValue.get_by_key_name('mandrill_api_key').value if api_key is None: raise Exception("No SecretValue set for 'mandrill_api_key'") json_mandrill['key'] = api_key # URL for Mandrill HTTP POST request url = "https://mandrillapp.com/api/1.0/messages/send.json" rpc = urlfetch.create_rpc() urlfetch.make_fetch_call( rpc, url=url, payload=json.dumps(json_mandrill), method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) try: result = rpc.get_result() if result.status_code == 200: text = result.content except urlfetch.DownloadError: # Request timed out or failed. logging.error('Email failed to send.') result = None return result
def reset(self, table_definitions): """Drop all given tables and re-created them. Takes a dictionary of table name to with CREATE TABLE query string. """ if not util.is_development(): raise Exception("You REALLY don't want to do that.") for table, definition in table_definitions.items(): self.query('DROP TABLE IF EXISTS `{}`;'.format(table)) self.query(definition)
def mail_log(self): body = self.body + self.get_recent_log() subject = self.subject + self.get_error_summary() if util.is_development(): logging.warning("This is a development environment." "Mail NOT really sent.") else: mail.send_mail(self.from_address, self.to_address, subject, body) self.last_email = self.now return (subject, body)
def call(api_path, payload): """Make a synchronous call to the Mandrill API. See https://mandrillapp.com/api/docs/ Args: api_path: str, just the last two parts of the path, e.g. 'messages/send.json'. Varies by what part of the API you're using. payload: dict, always omitting the api key, which is added here in this function. Returns: None or parsed JSON from Mandrill response. """ sv_entity = ndb.Key('SecretValue', 'mandrill_api_key').get() if sv_entity: payload['key'] = sv_entity.value else: if not util.is_development(): logging.error("No mandrill api key set in production!") payload['key'] = config.default_mandrill_api_key try: result = urlfetch.fetch( url='https://mandrillapp.com/api/1.0/{}'.format(api_path), payload=json.dumps(payload), method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'}, # Default deadline appears to be 5 seconds, based on the numerous # errors we're getting. Give the Mandrill API more time to respond. # https://cloud.google.com/appengine/docs/standard/python/refdocs/google.appengine.api.urlfetch#google.appengine.api.urlfetch.fetch deadline=60, # seconds ) except urlfetch.DownloadError: logging.error("Caught urlfetch.DownloadError. " "Request timed out or failed.") content = None else: if not result or result.status_code != 200: logging.error("Non-200 response from Mandrill.") content = None else: content = json.loads(result.content) logging.info("urlfetch result: {} {}".format(result.status_code, result.content)) return content
def init_database(self): """If necessary, create the database and populate it with tables.""" # Only do this in development, because it takes some small amount of time # out of each query. We're willing to pay that cost in development for # ease of quick deployment, but we want to optimize for production. if not util.is_development() or util.is_codeship(): return db_params = mysql_connection.get_params() with mysql_connection.connect(specify_db=False) as sql: params = (db_params['db_name'], ) rows = sql.query('SHOW DATABASES LIKE %s', param_tuple=params) if len(rows) == 0: sql.query('CREATE DATABASE `{}`'.format(db_params['db_name'])) sql.query('USE `{}`'.format(db_params['db_name'])) sql.reset({ m.table: m.get_table_definition() for m in get_sql_models() })
def connect_to_db(self): """Establish connection to MySQL db instance. Either Google Cloud SQL or local MySQL server. Detects environment with functions from util module. """ if util.is_localhost(): env_type = 'localhost' elif util.is_development(): env_type = 'development' else: env_type = 'production' creds = self.credentials[env_type] # Although the docs say you can specify a `cursorclass` keyword # here as an easy way to get dictionaries out instead of lists, that # only works in version 1.2.5, and App Engine only has 1.2.4b4 # installed as of 2015-03-30. Don't use it unless you know the # production library has been updated. # tl;dr: the following not allowed! # self.connection = MySQLdb.connect( # charset='utf8', cursorclass=MySQLdb.cursors.DictCursor, **creds) self.connection = MySQLdb.connect(charset='utf8', **creds)
import logging import random # ForgotPasswordHandler import string # ForgotPasswordHandler import time import traceback import urllib # ForgotPasswordHandler from core import * from named import * from api import kind_to_class from url_handlers import * import util # make sure to turn this off in production!! # it exposes exception messages debug = util.is_development() class ApiHandler(BaseHandler): """Superclass for all api-related urls.""" def dispatch(self): if self.datastore_connected(): # Call the overridden dispatch(), which has the effect of running # the get() or post() etc. of the inheriting class. BaseHandler.dispatch(self) else: # Sometimes the datastore doesn't respond. I really don't know why. # Wait a bit and try again. attempts = int(self.request.get('connection_attempts', 0)) + 1 if attempts <= 10: time.sleep(1)
def dispatch(self): """Wraps the other request handlers. * Manages sessions * Manages request profiling """ util.profiler.add_event("BaseHandler.dispatch()") # ** Code to run before all handlers goes here. ** # # The App Engine runtime does weird caching of classes and class # properties such that you can't expect them to be cleanly segregated # or reset between requests. But we want to use this property to avoid # multiple lookups of the same user within a request. So make sure it # has a clean start. # https://cloud.google.com/appengine/docs/standard/python/how-requests-are-handled#app-caching self._user = None if util.is_localhost(): # ports are arbitrary, but convenient os.environ['YELLOWSTONE_DOMAIN'] = 'localhost:9080' os.environ['YELLOWSTONE_PROTOCOL'] = 'http' os.environ['NEPTUNE_DOMAIN'] = 'localhost:8080' os.environ['NEPTUNE_PROTOCOL'] = 'http' os.environ['TRITON_DOMAIN'] = 'localhost:10080' os.environ['TRITON_PROTOCOL'] = 'http' else: # Various DOMAINs remain set as in app.yaml os.environ['YELLOWSTONE_PROTOCOL'] = 'https' os.environ['NEPTUNE_PROTOCOL'] = 'https' os.environ['TRITON_PROTOCOL'] = 'https' # Set the namespace, which varies by branch. namespace = os.environ['NAMESPACE'] if namespace: logging.info("Setting namespace: {}".format(namespace)) namespace_manager.set_namespace(namespace) # Newly deployed dev branches might not have a database in their # namespace yet. self.init_database() if self.using_sessions(): # Get a session store for this request. self.session_store = sessions.get_store(request=self.request) # Allow load testing services to log in quickly. if util.is_development() and self.request.get('demo_login', None) == 'wamxdkrwnkgey': user = User.get_by_id('User_demo') self.log_in(user) self.redirect(self.request.path) # Handler classes may set a class property `requires_auth` which triggers a check # for an authenticated user. If there isn't one, the request is immediately # rejeted with a 401. This does not apply to preflight OPTIONS calls which never # include Authorization headers (they're about figuring out the server's CORS # rules, not taking any actions). authed = getattr(self, 'requires_auth', False) options = self.request.method == 'OPTIONS' # This may be used by downstream handlers to override permissions if # necessary. self.allowed_by_jwt = self.jwt_allows_endpoint(self.get_endpoint_str()) if self.allowed_by_jwt: logging.info("BaseHandler: this request is ALLOWED by the jwt.") if authed and not options: user = self.get_current_user() if user.user_type == 'public' and not self.allowed_by_jwt: return self.http_unauthorized() try: # Call the overridden dispatch(), which has the effect of running # the get() or post() etc. of the inheriting class. webapp2.RequestHandler.dispatch(self) finally: # ** Code to run after all handlers goes here. ** # if self.using_sessions(): # Save all sessions. self.session_store.save_sessions(self.response) util.profiler.add_event("END") # Turn on for debugging/profiling. # logging.info(util.profiler) util.profiler.clear()
class CsvFile: """Uses Google Cloud Storage to manage csv files with certain presets.""" mime_type = 'text/plain' if util.is_development(): bucket = 'perts_dev' else: bucket = 'perts_prod' # You can specific some parameters here, see commented example. retry_params = gcs.RetryParams() # retry_params = gcs.RetryParams(initial_delay=0.2, # max_delay=5.0, # backoff_factor=2, # max_retry_period=15) # Only used during writes. Controls permissions. For types of permission, # see https://developers.google.com/storage/docs/accesscontrol#extension write_options = { 'x-goog-acl': 'project-private', # default permission # 'x-goog-meta-foo': 'foo', # arbitrary meta data # 'x-goog-meta-bar': 'bar', } @classmethod def list_bucket_files(klass, bucket_name): iterable = gcs.listbucket('/' + bucket_name + '/') return [f.filename for f in iterable] def __init__(self, filename): self.relative_path = filename self.absolute_path = '/{}/{}'.format(self.bucket, self.relative_path) # create the file, if it doesn't exist already # This doesn't work. try: gcs_file = gcs.open(self.absolute_path, mode='r', retry_params=self.retry_params) gcs_file.close() except: self.write(json.dumps([])) def read(self): gcs_file = gcs.open(self.absolute_path, mode='r', retry_params=self.retry_params) string = gcs_file.read() gcs_file.close() return string def write(self, string): gcs_file = gcs.open(self.absolute_path, mode='w', content_type=self.mime_type, retry_params=self.retry_params, options=self.write_options) # unicode not allowed here gcs_file.write(str(string)) gcs_file.close() @classmethod def delete(self, filename): """Delete method will remove a file from GCS, provided an absolute file path.""" try: gcs.delete(filename) return "{} deleted.".format(filename) except gcs.NotFoundError: return 'GCS File Not Found'