Currently, we keep secret like telegram token by hardcoding into the source code. It’s considered a security risk because those secrets can be extracted without much effort by hackers.

Today, we will upgrade our code to store secrets in Datastore.

config.py

import logging

from google.appengine.ext import ndb
from google.appengine.ext.ndb import model


class Config(ndb.Model):
    value = ndb.StringProperty()

    @classmethod
    def get(self, name):
        NOT_SET_VALUE = u'__ NOT SET __'

        entity = self(key=model.Key(self, name))
        entity.populate(value=NOT_SET_VALUE)
        txn = lambda: entity.put() if not entity.key.get() else entity.key
        retval = model.transaction(txn).get()

        if retval.value == NOT_SET_VALUE:
            logging.error((
                '%s %s not found in the database. A placeholder ' +
                'record has been created. Go to the Developers Console '
                'for your app in App Engine, look up the Settings record '
                'with name=%s and enter its value in that record\'s value '
                'field.'
                ) % (
                self.__name__,
                name,
                name)
                )

        return retval.value


TELEGRAM_BOT_TOKEN_KEY = "TELEGRAM_BOT_TOKEN_KEY"


class TelegramConfig(Config):
    def get_base_url(self):
        token = self.get(TELEGRAM_BOT_TOKEN_KEY)
        return 'https://api.telegram.org/bot{}'.format(token)

base.py

Create __init__ method to retrieve Telegram token and assign to self.telegramBaseUrl

[...]

from app.models.config import TelegramConfig

[...]

class SettingsPage(Handler):
    def __init__(self, *args, **kwargs):
        super(SettingsPage, self).__init__(*args, **kwargs)
        self.telegramBaseUrl = TelegramConfig().get_base_url()

    def __get_webhook_url(self):
        url = '{0}/getWebhookInfo'.format(self.telegramBaseUrl)
        try:
            result = urlfetch.fetch(url)
            if result.status_code == 200:
                jsonstr = result.content
                jsonobj = json.loads(jsonstr)
                return jsonobj['result']['url']
            else:
                return None
        except:
            return None

    def __set_webhook_url(self, new_url):
        url = '{0}/setWebhook?url={1}'.format(self.telegramBaseUrl, new_url)
        try:
            result = urlfetch.fetch(url)
            if result.status_code == 200:
                jsonstr = result.content
                jsonobj = json.loads(jsonstr)
                message = jsonobj['description']
                if 'error_code' in jsonobj:
                    success = False
                    error_code = jsonobj['error_code']
                    message = 'Code({0}) {1}'.format(error_code, message)
                else:
                    success = True
                return success, message
            else:
                return False, None
        except:
            return False, None
[...]

chat.py

Retrieve Telegram token from config and store in class variable.

[...]

from app.models.config import TelegramConfig

[...]

class Handler(object):
    def __init__(self, body):
        self.body = body
        self.message = self.__get_message()
        self.telegramBaseUrl = TelegramConfig().get_base_url()

    def send_reply(self):
        reply_method, data = self.get_reply()
        if data:
            requestUrl = '{}/{}'.format(self.telegramBaseUrl, reply_method)
            resp = urllib2.urlopen(requestUrl, data).read()
            logging.info('Reply is sent with response: {0}'.format(resp))
        else:
            logging.info('Send reply fail')
            resp = None
[...]

bot.py

Clean up by removing Telegram Token from source code. We will get it from datastore from now on.

import webapp2
import json
import logging

from google.appengine.api import urlfetch

import app.models.chat as chat


class WebhookHandler(webapp2.RequestHandler):
    def post(self):
        urlfetch.set_default_fetch_deadline(60)
        body = json.loads(self.request.body)
        if body:
            logging.info('Update body: {}'.format(body))
            chat_handler = chat.Handler(body)
            chat_handler.send_reply()
        else:
            logging.info('Telegram update body is Invalid')