from google.appengine.api import urlfetch, users from google.appengine.ext import db from google.appengine.ext.webapp import RequestHandler, WSGIApplication, template from google.appengine.ext.db import djangoforms from urllib import urlencode from wsgiref.handlers import CGIHandler try: from django import newforms as forms except ImportError: from django import forms try: from django.contrib.localflavor.us.forms import USPhoneNumberField except ImportError: from django.contrib.localflavor.usa.forms import USPhoneNumberField import logging, os, re logging.getLogger().setLevel(logging.DEBUG) CLICK2CALL_URL = 'https://secure.click2callu.com/tpcc/makecall' def vonageize_number(number): number = re.sub(r'\D', '', number) if len(number) == 0: raise forms.ValidationError('Please enter a telephone number.') if number[0] == '1': number = number[1:] if len(number) != 10: raise forms.ValidationError('Please enter a 10-digit US telephone ' 'number.') return '1' + number class VonageUSNumberProperty(db.PhoneNumberProperty): def validate(self, value): return vonageize_number(value) class VonageNumber(db.Model): label = db.StringProperty() username = db.StringProperty(required=True) password = db.StringProperty(required=True) number = VonageUSNumberProperty(required=True) class AllowedUser(db.Model): user = db.UserProperty(required=True) def login_required(f): def handle(self): user = users.get_current_user() if user is None: return self.redirect(users.create_login_url(self.request.uri)) if AllowedUser.gql('WHERE user = :1', user).get() is None: if users.is_current_user_admin(): AllowedUser(user=user).put() else: email = user.email() logging.error('Denied access to ' + email) return self.unavailable( 'Your account %s does not have permission to use ' 'this service.' % email) return f(self) return handle def admin_required(f): def handle(self): if not users.is_current_user_admin(): return self.unavailable( 'This function requires you to be an administrator.') return f(self) return handle class NumberForm(djangoforms.ModelForm): password = forms.CharField(widget=forms.widgets.PasswordInput) number = USPhoneNumberField(label='Vonage number') class Meta: model = VonageNumber exclude = ['label'] class BasePage(RequestHandler): def render(self, **kw): self.response.out.write(template.render(self.template_path, kw)) def unavailable(self, msg): self.render(unavailable=msg, logout_url=users.create_logout_url(self.request.uri)) class NumbersPage(BasePage): template_path = os.path.join(os.path.dirname(__file__), 'numbers.html') @admin_required def get(self): self.render(form=NumberForm(instance=VonageNumber.all().get())) @admin_required def post(self): form = NumberForm(data=self.request.POST, instance=VonageNumber.all().get()) if not form.is_valid(): return self.render(form=form) try: form.save() except Exception, e: return self.render(form=form, error=e.message) self.redirect('/') class MainPage(BasePage): template_path = os.path.join(os.path.dirname(__file__), 'index.html') def error(self, msg, **kw): self.render(error=msg, tonumber=self.request.get('tonumber', ''), **kw) def failure(self, msg): logging.error('TPCC server error: ' + msg) self.error('The Vonage Click2Call server could not complete your call.', failure=msg) def no_number(self): if users.is_current_user_admin(): return self.redirect('/numbers') self.unavailable('No phone numbers have been configured. Please ask an ' 'administrator to log in and add a phone number.') @login_required def get(self): if VonageNumber.all().get() is None: return self.no_number() self.render() @login_required def post(self): fromnumber = VonageNumber.all().get() if fromnumber is None: return self.no_number() try: tonumber = vonageize_number(self.request.get('tonumber', '')) except Exception, e: return self.error(e.message) body = urlencode( dict(username=fromnumber.username, password=fromnumber.password, fromnumber=fromnumber.number, tonumber=tonumber)) try: response = urlfetch.fetch(CLICK2CALL_URL + '?' + body) # get random auth errors with body, urlfetch.POST) except urlfetch.Error, e: return self.failure(e.message) if response.status_code != 200 or response.content[:3] != '000': return self.failure(response.content) logging.info('Dialing %s' % tonumber) return self.render( message='Dialing. Please answer your phone when it rings.') def main(): CGIHandler().run(WSGIApplication([('/', MainPage), ('/numbers', NumbersPage)], debug=True)) if __name__ == "__main__": main()