Skip to content

Commit

Permalink
split out enc/decode steps
Browse files Browse the repository at this point in the history
  • Loading branch information
smn committed Mar 11, 2014
1 parent 26a4a82 commit 4d98717
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 23 deletions.
30 changes: 30 additions & 0 deletions txsyncml/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ def create(cls, content):
class Cred(SyncMLElement):

allowed_children = [Meta, Data]
auth_decoders = {
'syncml:auth-basic': lambda d: base64.b64decode(d).split(':'),
}

@classmethod
def create(cls, username, password, auth_type='syncml:auth-basic'):
Expand All @@ -156,6 +159,33 @@ def create(cls, username, password, auth_type='syncml:auth-basic'):
Data.create(base64.b64encode('%s:%s' % (username, password)))
])

def decode_auth(self):
if hasattr(self, '_auth_cache'):
return getattr(self, '_auth_cache')

[meta] = self.find('Meta')
[type_] = meta.find('Type')
[data] = self.find('Data')

decoder = self.auth_decoders.get(type_.value)

if decoder is None:
raise SyncMLError('Auth type %r is not supported.' % (
type_.value))

self._auth_cache = decoder(data.value)
return self._auth_cache

@property
def username(self):
auth = self.decode_auth()
return auth[0]

@property
def password(self):
auth = self.decode_auth()
return auth[1]


class VerDTD(SyncMLElement):

Expand Down
64 changes: 42 additions & 22 deletions txsyncml/resource.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- test-case-name: txsyncml.tests.test_resource -*-

from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.internet.defer import maybeDeferred
from twisted.python import log
from twisted.web.resource import Resource
from twisted.web import http
Expand All @@ -12,7 +12,7 @@
from txsyncml.commands import (
SyncML, SyncHdr, SyncBody, Target, Source, Status)
from txsyncml.parser import SyncMLParser
from txsyncml.syncml import SyncMLEngine, UserState
from txsyncml.syncml import SyncMLEngine, UserState, AuthenticationBackend


class TxSyncMLError(Exception):
Expand All @@ -37,12 +37,17 @@ def __init__(self, reactor):
self.reactor = reactor

def render_POST(self, request):
d = Deferred()
d.addCallback(self.handle_request)
d = maybeDeferred(self.get_codec, request)
d.addCallback(self.handle_request, request)
d.addErrback(self.handle_request_error, request)
return NOT_DONE_YET

def handle_request(self, codec, request):
d = self.decode_request(request, codec)
d.addCallback(self.process_syncml, request)
d.addCallback(self.finish_request, request)
d.addCallback(self.encode_response, codec)
d.addCallback(self.finish_request, request, codec.content_type)
d.addErrback(self.handle_request_error, request)
reactor.callLater(0, d.callback, request)
return NOT_DONE_YET

def handle_request_error(self, failure, request):
Expand All @@ -51,8 +56,8 @@ def handle_request_error(self, failure, request):
log.err(failure)
error = TxSyncMLError(message='Internal server error.')

request.setHeader('Content-Type', 'text/plain')
request.setResponseCode(error.code)
request.setHeader('Content-Type', 'text/plain')
request.write(error.message)
request.finish()

Expand All @@ -62,16 +67,35 @@ def get_codec(self, request):
raise ContentTypeError()
return codec

def handle_request(self, request):
codec = self.get_codec(request)
return codec.decode(request.content.read())
def decode_request(self, request, codec):
d = codec.decode(request.content.read())
d.addCallback(SyncMLParser.parse)
return d

def process_syncml(self, syncml, request):
codec = self.get_codec(request)
def encode_response(self, doc, codec):
return codec.encode(doc.to_xml())

state = UserState()
syncml_engine = SyncMLEngine(state)
syncml_engine.process(SyncMLParser.parse(syncml))
def finish_request(self, response, request, content_type):
request.setHeader('Content-Type', content_type)
request.write(response)
request.finish()

def authenticate_request(self, doc):
header = doc.get_header()
[cred] = header.find('Cred')

auth = AuthenticationBackend()
return auth.authenticate(cred.username, cred.password)

def process_syncml(self, doc, request):
d = self.authenticate_request(doc)
d.addCallback(self.handle_authorized_syncml, doc)
d.addErrback(self.handle_unauthorized_syncml, doc)
return d

def handle_authorized_syncml(self, user_state, doc):
syncml_engine = SyncMLEngine(user_state)
syncml_engine.process(doc)

header = SyncHdr.create(
1, 1,
Expand All @@ -83,11 +107,7 @@ def process_syncml(self, syncml, request):
target_ref='http://www.syncml.org/sync-server',
source_ref='IMEI:493005100592800',
code=constants.AUTHENTICATION_ACCEPTED)])
syncml = SyncML.create(header=header, body=body)
return codec.encode(syncml.to_xml())
return SyncML.create(header=header, body=body)

def finish_request(self, response, request):
codec = self.get_codec(request)
request.setHeader('Content-Type', codec.content_type)
request.write(response)
request.finish()
def handle_unauthorized_syncml(self, failure):
return ''
8 changes: 8 additions & 0 deletions txsyncml/syncml.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# -*- test-case-name: txsyncml.tests.test_syncml -*-
from twisted.python import log
from twisted.internet.defer import succeed


class SyncMLException(Exception):
pass


class AuthenticationBackend(object):

def authenticate(self, username, password):
log.msg('Authenticating: %r' % (username,))
return succeed(UserState())


class UserState(object):

states = [
Expand Down
11 changes: 10 additions & 1 deletion txsyncml/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from txsyncml import constants
from txsyncml.commands import (
SyncML, SyncHdr, Target, Source, Cred, Meta, SyncBody, Item, Alert,
Anchor, Data, Status, Type)
Anchor, Data, Status, Type, SyncMLError)
from txsyncml.tests.test_base import TxSyncMLTestCase


Expand Down Expand Up @@ -49,6 +49,15 @@ def test_cred(self):
"<Data>%s</Data>"
"</Cred>" % (b64encode("foo:bar")))

def test_cred_properties(self):
cred = Cred.create('foo', 'bar', auth_type='syncml:auth-basic')
self.assertEqual(cred.username, 'foo')
self.assertEqual(cred.password, 'bar')

def test_cred_invalid_authtype(self):
cred = Cred.create('foo', 'bar', auth_type='foo')
self.assertRaises(SyncMLError, lambda: cred.username)

def test_meta(self):
meta = Meta.create([
Type.create('syncml:auth-basic'),
Expand Down

0 comments on commit 4d98717

Please sign in to comment.