Add web infrastructure borrowing them from my domotia daemon

parent 62f83c5d
......@@ -14,9 +14,24 @@ and Beaglebone like my [Osso](https://git.nexlab.net/nexlab/Osso) I/O expansion
Penguidom is written in python 2.x on top of the well known twisted framework,
and it uses few external libraries.
Other than twisted you will also need to install:
Other than twisted and newov you will also need to install:
* DMLib : https://git.nexlab.net/domotika/dmlib
* python-nexlibs: https://git.nexlab.net/nexlab/python-nexlibs
## WARNING: Penguidom isn't yet ready, i will remove this warning as soon as it will be usable.
\ No newline at end of file
* socketjs-twisted: https://github.com/DesertBus/sockjs-twisted
* corepost: https://github.com/nextime/corepost
### Paradox alarm panels:
At the moment only MG5050 panel is fully supported as it's the one i own.
To make other panels works you need to compile the panel registers file in
penguidom/plugins/paradox/panels/ like the MG5050.py one.
To make this easier, you can "sniff" the protocol communication between the panel and
a client side software like babyware or winload by installing them inside a virtual machine
( i use virtualbox for that ) and then attach the serial over TCP using the included serial proxy
in the paradox module.
## WARNING: Penguidom isn't yet ready, i will remove this warning as soon as it will be usable.
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
###########################################################################
# Copyright (c) 2011-2014 Unixmedia S.r.l. <info@unixmedia.it>
# Copyright (c) 2011-2014 Franco (nextime) Lanza <franco@unixmedia.it>
#
# Domotika System Controller Daemon "domotikad" [http://trac.unixmedia.it]
#
# This file is part of domotikad.
#
# domotikad is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import json
from twisted.internet import defer, task, reactor, protocol
from nevow import inevow, rend, tags, loaders, flat, athena, stan, guard
import logging, os, sys
from twisted.web import resource, static, server
import time
from txsockjs.factory import SockJSResource, SockJSFactory
import dmjson as dmj
log = logging.getLogger( 'Webgui' )
curdir=os.path.abspath(os.path.dirname(sys.argv[0]))
class AjaxProtocol(object):
def messageReceived(self, message, binary):
f=getattr(self, 'on_command_'+message.split(":")[0], None)
if f and callable(f):
log.debug("Received "+str(message.split(":")[0])+" command")
return f(message)
else:
self.messageSend(message, binary)
def _getIOStatus(self, d, ts):
rs = self.factory.core.getRelays(ts)
rs.addCallback(relstatus, False, d)
return rs.addCallback(dmj.jsonize_text)
def on_command_getIOStatus(self, data):
wts=data.split(":")[1].split("=")[1]
ret=self.factory.core.getActionStatus()
ts=int(time.time())-1
ist=self.factory.core.getInputs(wts)
ist.addCallback(inputstatus, ts, ret).addCallback(self._getIOStatus, wts)
return ist.addCallback(self.messageSend)
def connectionMade(self, who):
log.debug('AJAX connectionMade called by '+str(who))
def connectionLost(self, who, reason):
log.debug('AJAX connectionLost called by '+str(who)+' for reason '+str(reason))
class SockJSServerProtocolWrapper(protocol.Protocol, AjaxProtocol):
def dataReceived(self, data):
log.debug("SockJS RECEIVED: "+str(data))
self.messageReceived(data, False)
def messageSend(self, msg, binary=False):
self.transport.write(msg)
def connectionMade(self):
AjaxProtocol.connectionMade(self, 'sockjs')
def connectionLost(self, reason):
AjaxProtocol.connectionLost(self, 'sockjs', reason)
class SockJSServerFactory(protocol.Factory):
protocol = SockJSServerProtocolWrapper
def buildProtocol(self, addr):
log.debug("SockJS BuildProtocol for "+str(addr))
p = self.protocol()
p.core = self.core
p.factory = self
return p
def getSocketJSResource(core):
options = {
'websocket': True,
'cookie_needed': False,
'heartbeat': 25,
'timeout': 5,
'streaming_limit': 128 * 1024,
'encoding': 'cp1252', # Latin1
#'sockjs_url': '/resources/js/sockjs-0.3.min.js'
#'sockjs_url': 'https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.js'
}
f = SockJSServerFactory()
f.core = core
return SockJSResource(f, options)
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from twisted.web import client
from zope.interface import implements
from twisted import cred
import twisted.cred.portal
import twisted.cred.credentials
import twisted.cred.checkers
import twisted.cred.error
import logging
from twisted.internet import defer
try:
import hashlib
md5 = hashlib
md5.new = hashlib.md5
sha1 = hashlib.sha1
except:
import md5
import sha1
from nexlibs.utils import genutils
log = logging.getLogger("Webgui")
class mindFactory(object):
loginsaved = False
def __init__(self, request, credential):
self.request = request
self.credential = credential
self.args = self.request.args
class clientAuth(object):
implements(cred.checkers.ICredentialsChecker)
credentialInterfaces = (
cred.credentials.IUsernamePassword,
)
def __init__(self):
pass
def checkAuth(self, usr, pwd):
log.debug("CheckAuth for "+str(usr)+" "+str(pwd))
return self.core.getAuth(usr, genutils.hashPwd(pwd))
def getAuth(self, usr, pwd):
log.debug("getAuth for "+str(usr)+" "+str(pwd))
return self.checkAuth(usr, pwd).addCallback(self.getPerms, pwd)
def getPerms(self, res, pwd):
log.info("getPerms "+str(res))
"""
if len(res) > 0:
if res[0].admin == True:
perms['admin'] = True
return perms
"""
if len(res) > 0:
perms = res[0]
perms.loginpwd = pwd
return perms
return False
def requestAvatarId(self, c):
log.debug('AUTH: '+str(c)+" "+str(c.username))
return self.checkAuth(c.username, c.password).addCallback(
self.getPerms, c.password).addCallback(
self.AvatarResults, c)
def AvatarResults(self, res, c):
log.debug("AvatarResults "+str(res)+" "+str(c))
if res:
return defer.succeed([c, res])
raise cred.error.UnauthorizedLogin()
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from zope.interface import Interface, implements
from twisted.python import components
from nevow import rend, inevow
from corepost import Response, NotFoundException, AlreadyExistsException
from corepost.web import RESTResource, route, Http
from corepost.convert import convertToJson
from corepost.enums import MediaType, HttpHeader
from nexlibs.utils import webutils as wu
import hashlib
import hmac
import six
import time
from Queue import Queue
import uuid
from domotika.singleton import messengerlinks
from StringIO import StringIO
import imghdr
_MESSENGER_LINKS = messengerlinks.MessengerLinkRegistry()
_MESSENGER_PSID = messengerlinks.MessengerPSIDRegistry()
try:
#python2
from urllib import urlencode
except ImportError:
#python3
from urllib.parse import urlencode
import logging
log = logging.getLogger( 'Webgui' )
BOTResource = RESTResource
class IBotProtoInterface(Interface):
def getUsername(self, uid):
""" get a username from uid """
def isLogged(self, uid):
""" return True if it's a know user """
def sendAPI(self, payload, req_uri):
""" Send request to the API """
def receivedAuthentication(self, msg):
""" receive auth message """
def receivedTextMessage(self, msg):
""" receive text message """
def receivedDeliveryConfirmation(self, msg):
""" receive delivery confirmation """
def receivedPostback(self, msg):
""" receive a postback message """
def receivedAccountLink(self, msg):
""" receive account linking message """
def sendCommandList(self, recipient_id):
""" send command list to the user """
def sendScreenshotList(self, recipient_id):
""" send a list of screenshots available """
def sendScreenshot(self, recipient_id, target):
""" send one or more screenshots """
def sendClima(self, recipient_id):
""" send Clima status """
def sendMessage(self, recipient_id, message):
""" send a text message """
def sendImageMessage(self, recipient_id, imguri):
""" send an image link """
def sendImageMessageData(self, recipient_id, imagedata, mtype):
""" send an image by data """
def sendAuthRequest(self, recipient_id):
""" send login request """
class BotProto(object):
def __init__(self, core):
self.core = core
def botCommandParser(self, uid, txt):
def voiceResult(res):
log.info('VoiceResult: '+str(res))
if len(res) > 0:
if res[0] == 'Ok' and len(res) > 1:
try:
result = 'ho eseguito "'+" ".join(res[1]['clean'])+'"'
except:
pass
else:
result = 'Spiacente, non ho trovato un comando corrispondente'
self.sendMessage(uid, result)
txt = unicode(txt.lower())
if not self.isLogged(uid):
if txt == u'hello' or txt == u'ciao':
self.sendMessage(uid, 'Hello, how can i help you?')
elif txt == u'?' or txt == u'help':
self.sendMessage(uid, 'you can\'t do that.')
elif txt == u'login':
self.sendAuthRequest(uid)
else:
self.sendMessage(uid, txt+u" come se fosse antani")
else:
if txt == u'hello' or txt == u'ciao':
self.sendMessage(uid, 'Hello %s, how can i help you?' %(self.getUsername(uid)))
elif txt == u'?' or txt == u'help':
self.sendMessage(uid, 'Ok, devo ancora implementare l\'aiuto! Sorry for that!')
self.sendMessage(uid, 'Anyway, i comandi che hai sono: "command list", "screenshot list", "screenshot" e "clima".')
self.sendMessage(uid, 'Qualsiasi altro comando viene interpretato come un potenziale comando vocale.')
elif txt == u'logout':
self.sendMessage(uid, 'Ok, devo ancora implementare anche il logout')
elif txt == u'login':
self.sendMessage(uid, 'Sei gia\' loggato, %s!' %(self.getUsername(uid)))
elif txt == u'command list' or txt == 'cmd list':
self.sendCommandList(uid)
elif txt == u'screenshot list' or txt==u'ss list':
self.sendScreenshotList(uid)
elif txt.startswith('screenshot ') or txt.startswith('ss '):
self.sendScreenshot(uid, " ".join(txt.split()[1:]))
elif txt == u'clima':
self.sendClima(uid)
else:
self.core.voiceReceived(txt, confidence=1.0, lang="it").addCallback(voiceResult)
class BotCore(object):
path = ""
def __init__(self, core, session):
self.core = core
self.session = session
class BaseBot(BotCore):
@route("/")
def welcome(self, request, *a, **kw):
return 'Domotika BOTs API interface V1.0'
def messengerValidator():
def checkRequest(f):
def new_f(self, request, *a, **kw):
if 'x-hub-signature' in request.getAllHeaders().keys():
hubsig=request.getHeader('x-hub-signature')
try:
hash_method, hub_signature = hubsig.split('=')
except:
pass
else:
digest_module = getattr(hashlib, hash_method)
hmac_object = hmac.new(str(self._cfgGet('app_secret')), unicode(request.content.getvalue()), digest_module)
generated_hash = hmac_object.hexdigest()
if hub_signature == generated_hash:
return f(self, request, *a, **kw)
return Response(200, "Failed validation." )
new_f.funct_name = f.func_name
return new_f
return checkRequest
class MessengerMessage(object):
def __init__(self, headers, payload, uri):
self.headers = headers
self.payload = payload
self.uri = uri
class MessengerConf(object):
def _cfgGet(self, keyword):
return self.core.configGet('messenger', keyword)
class MessengerBotAdapter(MessengerConf):
implements(IBotProtoInterface)
graphuri = 'https://graph.facebook.com/v2.6'
sendQueue = Queue()
sendlock = False
def __init__(self, orig):
self.orig = orig
self.core = orig.core
@property
def auth_args(self):
if not hasattr(self, '_auth_args'):
auth = {
'access_token': unicode(self._cfgGet('page_token'))
}
if self._cfgGet('app_secret') is not None and len(self._cfgGet('app_secret'))>0:
appsecret_proof = self._generateAppSecProof()
auth['appsecret_proof'] = appsecret_proof
self._auth_args = auth
return self._auth_args
def _generateAppSecProof(self):
if six.PY2:
hmac_object = hmac.new(str(self._cfgGet('app_secret')), unicode(self._cfgGet('page_token')), hashlib.sha256)
else:
hmac_object = hmac.new(bytearray(self._cfgGet('app_secret'), 'utf8'), str(self._cfgGet('page_token')).encode('utf8'), hashlib.sha256)
return hmac_object.hexdigest()
def _dataSent(self, res):
log.debug('Messenger BOT Datasent OK')
log.debug(res)
def _dataError(self, res):
log.info('Messenger BOT Datasent ERROR')
log.error(res)
try:
log.debug(res.value.reasons[0].printTraceback())
except:
pass
def _sendRaw(self):
if not self.sendQueue.empty() and not self.sendLock:
message = self.sendQueue.get()
return wu.getPage(message.uri, method='POST', headers=message.headers,
postdata=message.payload).addCallbacks(self._dataSent, self._dataError)
def sendAPI(self, payload, req_uri='/me/messages'):
request_endpoint = self.graphuri+req_uri
request_uri = request_endpoint+'?'+urlencode(self.auth_args)
log.info("Sending message to "+str(request_uri)+" : "+str(convertToJson(payload)))
ctype={"Content-Type": "application/json"}
return wu.getPage(request_uri, method='POST', headers=ctype, postdata=convertToJson(payload)).addCallbacks(self._dataSent, self._dataError)
def getUsername(self, uid):
return _MESSENGER_PSID.get_link(uid)
def isLogged(self, uid):
return _MESSENGER_PSID.linkid_exists(uid)
def receivedAuthentication(self, msg):
log.info("Messenger bot received authentication: "+str(msg))
def receivedTextMessage(self, msg):
log.info("Messenger bot received message: "+str(msg))
senderid = msg['sender']['id']
if not 'is_echo' in msg['message'].keys() or not msg['message']['is_echo']:
message = u'Received: '+unicode(msg['message']['text'])
txt = unicode(msg['message']['text']).lower()
botCommandParser(self, senderid, txt)
def receivedDeliveryConfirmation(self, msg):
log.info("Messenger bot received delivery confirmation: "+str(msg))
def receivedPostback(self, msg):
log.info("Messenger bot received postback: "+str(msg))
def receivedMessageRead(self, msg):
log.info("Messenger bot received message read: "+str(msg))
def receivedAccountLink(self, msg):
log.info("Messenger bot received accountlink: "+str(msg))
senderid = msg['sender']['id']
recipient = msg['recipient']['id']
status = msg['account_linking']['status']
authcode = msg['account_linking']['authorization_code']
if status == u'linked':
self.core.add_messenger_psid(senderid, authcode)
def sendCommandList(self, senderid):
def pushList(res):
for r in res:
self.sendMessage(senderid, " * "+str(r[0]))
self.sendMessage(senderid, "Target list for string/voice commands (call them with the right action like up,down,open,close and so on.):")
self.core.getVoiceCommandList().addCallback(pushList)
def sendScreenshotList(self, recipient_id):
def pushList(res):
for r in res:
self.sendMessage(recipient_id, " * "+str(r[0]))
self.sendMessage(recipient_id, "Cam list:")
self.core.getScreenshotList().addCallback(pushList)
def sendScreenshot(self, recipient_id, target):
def pushImage(res):
if res:
self.sendImageMessageData(recipient_id, res)
else:
self.sendMessage(recipient_id, 'Cannot retrieve image of '+str(target))
if target.endswith('%'):
target=target[:-1]
if len(target) > 0:
self.core.getScreenshot(target, pushImage)
def sendClima(self, recipient_id):
def pushClima(res):
for r in res:
if r[0] == 'last.rain':
msg=r[0]+": "+time.strftime('%d-%m-%Y %H:%M:%S', time.localtime(int(r[1])))
else:
msg=r[0]+": "+str(r[1])
self.sendMessage(recipient_id, msg)
def pushThermo(res):
for r in res:
msg=r[0]+": set "+str(r[1])+" read "+str(float(r[2])/10)+" function "+r[3]
self.sendMessage(recipient_id, msg)
return self.core.getClimaUniques().addCallback(pushClima)
self.core.getThermostats().addCallback(pushThermo)
def sendMessage(self, recipient_id, message):
payload = {
'recipient': {
'id': recipient_id
},
'message': {
'text': message
}
}
return self.sendAPI(payload)
def sendImageMessage(self, recipient_id, imageuri):
payload = {
'recipient': {
'id': recipient_id
},
'message': {
'attachment': {
'type': 'image',
'payload': {
'url': imageuri
}
}
}
}
return self.sendAPI(payload)
def sendImageMessageData(self, recipient_id, imagedata, mtype=None):
if not imagedata:
return
if mtype == None:
ft = imghdr.what(None, imagedata)
log.info("FILE TYPE: "+str(ft))
if ft in ['jpeg', 'gif', 'png']:
mtype=ft
else:
log.warning("Image mimetype doesn't seems to be valid: "+str(ft))
log.warning("Assuming it's a jpeg.")
mtype='jpeg'
fname=str(uuid.uuid4().get_hex())+mtype
bond="------------------------"+str(uuid.uuid4().get_hex())[:16]
data="--"+bond+"\r\n"
data+="Content-Disposition: form-data; name=\"recipient\"\r\n\r\n"
data+="{\"id\": \""+str(recipient_id)+"\"}\r\n"
data+="--"+bond+"\r\n"
data+="Content-Disposition: form-data; name=\"message\"\r\n\r\n"
data+='{"attachment":{"type":"image", "payload":{}}}'+"\r\n"
data+="--"+bond+"\r\n"
data+='Content-Disposition: form-data; name="filedata"; filename="'+fname+'"'+"\r\n"
data+='Content-Type: image/'+mtype+"\r\n\r\n"
data+=str(imagedata)
data+="\r\n--"+bond+"--\r\n"
headers={
'Accept': '*/*',
'Content-Type': 'multipart/form-data; boundary='+bond
}
request_endpoint=self.graphuri+'/me/messages'
request_uri = request_endpoint+'?'+urlencode(self.auth_args)
return wu.getPage(request_uri, agent="curl/7.50.1", method='POST', headers=headers,
postdata=data, expect100=True).addCallbacks(self._dataSent, self._dataError)
def sendAuthRequest(self, recipient_id):
payload = {
'recipient': {
'id': recipient_id
},
'message': {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [{
"title": self._cfgGet('auth_title'),
"image_url": self._cfgGet('auth_img'),
"buttons": [{
"type": "account_link",
"url": self._cfgGet('auth_host')
}]
}]
}
}
}
}
return self.sendAPI(payload)
components.registerAdapter(
MessengerBotAdapter,
BotProto,
IBotProtoInterface
)
class MessengerBot(BotCore, MessengerConf):
path = "messenger"
@route("/", Http.GET)
def rootGet(self, request, *a, **kw):
if 'hub.mode' in kw.keys() and kw['hub.mode'] == 'subscribe' and 'hub.challenge' in kw.keys():
if 'hub.verify_token' in kw.keys() and kw['hub.verify_token'] == self._cfgGet('verify_token'):
log.info("New verification request for Messenger BOT: "+str(kw))
return kw['hub.challenge']
return Response(403, "Failed validation." )
@route("/", Http.POST)
@messengerValidator()
def rootPost(self, request, *a, **kw):
log.debug("New messenger bot request: "+str(kw))
botproto = BotProto(self.core)
botiface = IBotProtoInterface(botproto)
if 'object' in kw.keys() and kw['object'] == 'page' and 'entry' in kw.keys():
for entry in kw['entry']:
#try:
if True:
pageID=entry['id']
timestamp=entry['time']
messaging=entry['messaging']
for message in messaging:
msgkeys = message.keys()
if 'optin' in msgkeys:
botiface.receivedAuthentication(message)
elif 'message' in msgkeys:
botiface.receivedTextMessage(message)
elif 'delivery' in msgkeys:
botiface.receivedDeliveryConfirmation(message)
elif 'postback' in msgkeys:
botiface.receivedPostback(message)
elif 'read' in msgkeys:
botiface.receivedMessageRead(message)
elif 'account_linking' in msgkeys:
botiface.receivedAccountLink(message)
else:
log.info("Received unknow messaging webhook for messenger BOT: "+str(kw))
#except:
# pass
return Response(200, 'OK')
@route("/loginfailed")
def loginFailed(self, request, *a, **kw):
k = kw.keys()
if 'account_linking_token' in k and 'redirect_uri' in k:
log.info("Passed linking uri "+kw['redirect_uri'])
luri=kw['redirect_uri']
log.info("Authorization failed: redirecting to "+luri)
return Response(302, 'Authorization failed', headers={'Location': luri})
return Response(200, "Authorization failed." )
class MessengerBotAuth(BotCore, MessengerConf):
path="messenger"
@route("/", Http.GET)
def linkUser(self, request, *a, **kw):
k = self.session.saveargs.keys()
if 'account_linking_token' in k and 'redirect_uri' in k:
luri=self.session.saveargs['redirect_uri'][0]
log.debug(str(self.session.mind.perms))
hmac_object = hmac.new(str(self.session.mind.perms.username), unicode(self._cfgGet('page_token')), hashlib.sha256)
_MESSENGER_LINKS.add_link(hmac_object.hexdigest(), self.session.mind.perms)
luri = luri+"&authorization_code="+hmac_object.hexdigest()
log.info("Authorization succeed: redirecting to "+luri)
headers={'Location': luri}
return Response(302, 'Authorization succeed', headers=headers)
return Response(200, "Authorization Succeed." )
BotApiList=(
BaseBot,
MessengerBot
)
class BotPages(rend.Page):
def child_(self, ctx):
request = inevow.IRequest(ctx)
self.session = inevow.ISession(ctx)
request.setHeader("pragma", "no-cache")
request.postpath=['/']
return BOTResource((BaseBot(self.core, self.session),))
def childFactory(self, ctx, name):
request = inevow.IRequest(ctx)
self.session = inevow.ISession(ctx)
request.setHeader("pragma", "no-cache")
#request.postpath=['/', name]+request.postpath
request.postpath=['/']+request.postpath
log.info("BOT: "+str(request.postpath))
return BOTResource([x(self.core, self.session) for x in BotApiList])
BotAuthList=(
BaseBot,
MessengerBotAuth
)
class BotAuth(rend.Page):
def childFactory(self, ctx, name):
request = inevow.IRequest(ctx)
self.session = inevow.ISession(ctx)
request.setHeader("pragma", "no-cache")
#request.postpath=['/', name]+request.postpath
request.postpath=['/']+request.postpath
log.info("BOTAUTH: "+str(request.postpath))
return BOTResource([x(self.core, self.session) for x in BotAuthList])
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import struct
import zlib
from twisted.web import http, server, resource, static, client
import random
import time
try:
import hashlib
md5 = hashlib
md5.new = hashlib.md5
sha1 = hashlib.sha1
except:
import md5
import sha1
import logging
from nexlibs.utils.blockingdefer import blockingDeferred
import cgi, logging
from zope.interface import implements
from twisted.cred import portal, checkers, credentials
import cgi, logging
from nevow import inevow, rend, tags, loaders, flat, athena, stan, guard
import os, sys
from twisted.python import reflect
from twisted import cred
import twisted.cred.portal
import twisted.cred.credentials
import twisted.cred.checkers
import twisted.cred.error
from twisted.internet import defer
from nevow import appserver
from twisted.web.twcgi import CGIScript, FilteredScript
log = logging.getLogger( 'Webgui' )
curdir=os.path.abspath(os.path.dirname(sys.argv[0]))
EXCLUDELIST=(
'.swf',
'.png',
'.jpg',
'.gif',
'.gz',
'.tgz'
'.zip'
)
class GzipRequest(object):
"""Wrapper for a request that applies a gzip content encoding"""
def __init__(self, request, compressLevel=6):
self.request = request
self.request.setHeader('Content-Encoding', 'gzip')
# Borrowed from twisted.web2 gzip filter
self.compress = zlib.compressobj(compressLevel, zlib.DEFLATED,
-zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL,0)
def __getattr__(self, attr):
if 'request' in self.__dict__:
return getattr(self.request, attr)
else:
raise AttributeError, attr
def __setattr__(self, attr, value):
if 'request' in self.__dict__:
return setattr(self.request, attr, value)
else:
self.__dict__[attr] = value
def write(self, data):
if not self.request.startedWriting:
self.crc = zlib.crc32('')
self.size = self.csize = 0
# XXX: Zap any length for now since we don't know final size
if 'content-length' in self.request.headers:
del self.request.headers['content-length']
# Borrow header information from twisted.web2 gzip filter
self.request.write('\037\213\010\000' '\0\0\0\0' '\002\377')
self.crc = zlib.crc32(data, self.crc)
self.size += len(data)
cdata = self.compress.compress(data)
self.csize += len(cdata)
if cdata:
self.request.write(cdata)
elif self.request.producer:
# Simulate another pull even though it hasn't really made it
# out to the consumer yet.
self.request.producer.resumeProducing()
def finish(self):
remain = self.compress.flush()
self.csize += len(remain)
if remain:
self.request.write(remain)
self.request.write(struct.pack('<LL',
self.crc & 0xFFFFFFFFL,
self.size & 0xFFFFFFFFL))
self.request.finish()
class StaticFile(static.File):
def render(self, request):
# Some flash file ( flowplayer.. ) needs
# to be server without gzip compression or
# they will not work sometime...
if self.basename().endswith(EXCLUDELIST):
return static.File.render(self, request)
accept_encoding = request.getHeader('accept-encoding')
if accept_encoding:
encodings = accept_encoding.split(',')
for encoding in encodings:
name = encoding.split(';')[0].strip()
if name == 'gzip':
request = GzipRequest(request)
break
return static.File.render(self, request)
class codeOk(rend.Page):
addSlash = True
content = 'OK'
def renderHTTP( self, ctx):
request = inevow.IRequest(ctx)
#request.setHeader('Location', '/')
request.setResponseCode(200)
return self.content
def setContent(self, cont):
self.content = str(cont)
def childFactory(self, ctx, data):
return self
class permissionDenied(rend.Page):
addSlash = True
def renderHTTP( self, ctx):
log.debug("Rendering Permission Denied")
request = inevow.IRequest(ctx)
#request.setHeader('Location', '/')
#request.setResponseCode(302)
request.setResponseCode(403)
html = '<html><head><title>Penguidom GUI</title></head>'
html = html+"<h3>I'm so sorry!</h3>"
html = html+"<p>you can't do that.</p>"
html = html+"<p><a href=\"/\">Go Home</a></p>"
html = html+'</body></html>'
return html
def childFactory(self, ctx, data):
return self
class RedirectToHome(rend.Page):
addSlash = True
def renderHTTP( self, ctx):
request = inevow.IRequest(ctx)
request.setHeader('Location', '/')
request.setResponseCode(302)
return 'Redirecting...'
def childFactory(self, ctx, data):
return self
def uni(s):
if isinstance(s, str):
return s.decode('utf-8', "replace")
elif isinstance(s, unicode):
return s.encode('utf-8', "replace")
else:
return s
class Caller(object):
def __init__(self, skobj):
self.domotika = skobj
self.missing_method_name = None # Hack!
def __getattribute__(self, name):
sk = object.__getattribute__(self, 'domotika')
try:
return object.__getattribute__(sk, name)
except:
self.missing_method_name = name
log.debug("Missing Method: "+str(name))
return object.__getattribute__(self, '__methodmissing__')
def __methodmissing__(self, *args, **kwargs):
log.debug("Missing Client method %s called (args = %s) and (kwargs = %s)" % (object.__getattribute__(self, 'missing_method_name'), str(args), str(kwargs)))
def neededPermission(method):
if method in ['GET','OPTIONS','HEAD','REPORT','CHECKOUT','SEARCH']:
return 'r'
else:
return 'w'
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from twisted.internet import defer, task, reactor, protocol
from nevow import inevow, rend, tags, loaders, flat, athena, stan, guard
from twisted.web import http, resource, static, server
import time
from common import permissionDenied, uni
from corepost import Response, NotFoundException, AlreadyExistsException
from corepost.web import RESTResource, route, Http
from corepost.convert import convertForSerialization, generateXml, convertToJson
from corepost.enums import MediaType, HttpHeader
import yaml
from functools import wraps, partial
from twisted.web.http import Request
from nexlibs.utils import genutils
import logging, os, sys
log = logging.getLogger( 'Webgui' )
curdir=os.path.abspath(os.path.dirname(sys.argv[0]))
class RestPages(rend.Page):
def child_(self, ctx):
return permissionDenied()
def childFactory(self, ctx, name):
log.debug("No child found (%s)" % name)
return permissionDenied()
def locateChild(self, ctx, segments):
name=segments[0]
session = inevow.ISession(ctx)
if len(segments)>1:
r=RestV12Pages()
r.core=self.core
r.session=session
request = inevow.IRequest(ctx)
request.postpath=list(segments[1:])
return r, segments[1:]
return rend.Page.locateChild(self, ctx, name)
class ResponseConversion(object):
def __init__(self, request, code=200, entity=None, headers={}, ctype=None):
self.code = code
self.entity=entity if entity != None else ""
self.headers=headers
self.request=request
self.ctype=ctype
self.serialized = convertForSerialization(self.entity)
if ctype=='json':
self.__convertToJson()
elif ctype=='jsonp':
self.__convertToJsonp()
elif ctype=='xml':
self.__convertToXml()
elif ctype=='yaml':
self.__convertToYaml()
else:
self.__automagickConversion()
def __convertToJson(self):
self.headers[HttpHeader.CONTENT_TYPE]=MediaType.APPLICATION_JSON
self.response=Response(self.code, convertToJson(self.serialized), self.headers)
def __convertToJsonp(self):
self.headers[HttpHeader.CONTENT_TYPE]=MediaType.APPLICATION_JSON
callback=""
if 'callback' in self.request.args.keys():
callback=self.request.args['callback'][0]
self.response=Response(self.code, callback+"("+convertToJson(self.serialized)+")", self.headers)
def __convertToXml(self):
self.headers[HttpHeader.CONTENT_TYPE]=MediaType.APPLICATION_XML
self.response=Response(self.code, generateXml(self.serialized), self.headers)
def __convertToYaml(self):
self.headers[HttpHeader.CONTENT_TYPE]=MediaType.TEXT_YAML
self.response=Response(self.code, yaml.dump(self.serialized), self.headers)
def __automagickConversion(self):
if self.request.path.endswith('/json'):
self. __convertToJson()
elif self.request.path.endswith('/jsonp'):
self. __convertToJsonp()
elif self.request.path.endswith('/xml'):
self. __convertToXml()
elif self.request.path.endswith('/yaml'):
self.__convertToYaml()
else:
if HttpHeader.ACCEPT in self.request.received_headers:
accept = self.request.received_headers[HttpHeader.ACCEPT]
if MediaType.APPLICATION_JSON in accept:
self. __convertToJson()
elif MediaType.TEXT_YAML in accept:
self.__convertToYaml()
elif MediaType.APPLICATION_XML in accept or MediaType.TEXT_XML in accept:
self. __convertToXml()
else:
self. __convertToJson()
else:
self. __convertToJson()
def getResponse(self):
return self.response
def wrapResponse(f=None, uri=False, res_filter=None, *a, **kw):
if f and not callable(f):
uri=f
f=None
if f is None:
return partial(wrapResponse, uri=uri, *a, **kw)
def okResponse(res, u):
if isinstance(res, ResponseConversion):
entity=res.entity
if res_filter and callable(res_filter):
entity=res_filter(entity)
elif res_filter and hasattr(res_filter, '__iter__'):
for fil in res_filter:
if callable(fil):
entity=fil(entity)
entity={'result': 'succeed', 'data': entity, 'ts': time.time()}
if int(res.code) >= 400:
entity['result']='fail'
if uri:
entity['uri']=uri
elif u:
entity['uri']=u
r = ResponseConversion(res.request, res.code, entity, res.headers, res.ctype).getResponse()
else:
if res_filter and callable(res_filter):
res=res_filter(res)
elif res_filter and hasattr(res_filter, '__iter__'):
for fil in res_filter:
if callable(fil):
res=fil(res)
r={'result': 'succeed', 'data': res, 'ts': time.time()}
if uri:
r['uri']=uri
elif u:
r['uri']=u
return r
def errorResponse(res, u):
if isinstance(res, ResponseConversion):
entity={'result': 'fail', 'data': res.entity, 'ts': time.time()}
if uri:
entity['uri']=uri
elif u:
entity['uri']=u
r = ResponseConversion(res.request, res.code, entity, res.headers, res.ctype).getResponse()
else:
r={'result': 'fail', 'data': res, 'ts': time.time()}
if uri:
r['uri']=uri
elif u:
r['uri']=u
return r
@wraps(f)
def decorate(*a, **kw):
ruri=False
if len(a) > 1 and isinstance(a[1], Request):
ruri=a[1].uri
ret=defer.maybeDeferred(f, *a, **kw)
ret.addCallback(okResponse, ruri)
ret.addErrback(errorResponse, ruri)
return ret
return decorate
class RestCore(object):
path = ""
def __init__(self, core, session):
self.core = core
self.session = session
def callbackResponse(self, d, request, search=False):
def okResponse(res):
if (('__len__' in dir(res) and len(res)==0) or (not res and res!=0) ) and not search:
return ResponseConversion(request, code=404, entity=res)
else:
return ResponseConversion(request, code=200, entity=res)
def errorResponse(res):
return ResponseConversion(request, code=500, entity='Server Error')
return d.addCallbacks(okResponse, errorResponse)
def positiveCallback(self, d, request, search=False):
def okResponse(res):
if (('__len__' in dir(res) and len(res)==0) or not res or (genutils.is_number(res) and res==0)) and not search:
return ResponseConversion(request, code=404, entity=res)
else:
return ResponseConversion(request, code=200, entity=res)
def errorResponse(res):
return ResponseConversion(request, code=500, entity='Server Error')
return d.addCallbacks(okResponse, errorResponse)
def _getRequestArgs(self, request):
rargs={}
for k in request.args.keys():
rargs[k]=request.args[k][0]
if len(rargs.keys())==0 and request.method=="PUT":
if(request.content.getvalue()!=""):
try:
# NOTE: workaround for PUT empry args
r = http.parse_qs(request.content.getvalue(), keep_blank_values=1)
for k in r.keys():
rargs[k]=r[k][0]
except:
pass
if request.method in (Http.POST,Http.PUT) and HttpHeader.CONTENT_TYPE in request.received_headers.keys():
contentType = request.received_headers["content-type"]
if contentType.split(";")[0] == MediaType.APPLICATION_JSON:
try:
request.json = json.loads(request.data) if request.data else {}
try:
r = dict(rargs.items() + request.json.items())
except:
r = request.json
except Exception as ex:
raise TypeError("Unable to parse JSON body: %s" % ex)
elif contentType.split(";")[0] in (MediaType.APPLICATION_XML,MediaType.TEXT_XML):
try:
request.xml = ElementTree.XML(request.data)
try:
r = dict(rargs.items() + request.xml.items())
except:
r = request.xml
except Exception as ex:
raise TypeError("Unable to parse XML body: %s" % ex)
elif contentType.split(";")[0] == MediaType.TEXT_YAML:
try:
request.yaml = yaml.safe_load(request.data)
try:
r = dict(rargs.items() + request.xml.yaml())
except:
r = request.xml.yaml
except Exception as ex:
raise TypeError("Unable to parse YAML body: %s" % ex)
else:
r = rargs
else:
r = rargs
return r
class BaseRest(RestCore):
@route("/")
def welcome(self, request, *a, **kw):
return 'Welcome to the Domotika REST API v1.2'
@route("/keepalive")
@wrapResponse
def keepAlive(self, request, *a, **kw):
return {'time':time.time()}
@route("/daemonstatus")
@wrapResponse
def daemonStatus(self, request, *a, **kw):
return ResponseConversion(request, entity=self.core.getDaemonStatus())
class BoardRest(RestCore):
path="boards"
@route("/")
def boardlist(self, request, *a, **kw):
return 'boardlist'
@route("/forceautodetect")
@wrapResponse
def boardForceAutodetect(self, request, *a, **kw):
self.core.startAutoDetection(True)
return ResponseConversion(request, entity='OK')
@route("/autodetect")
@wrapResponse
def boardAutodetect(self, request, *a, **kw):
self.core.startAutoDetection()
return ResponseConversion(request, entity='OK')
@route("/syncall")
@wrapResponse
def boardSyncAll(self, request, *a, **kw):
self.core.startSync()
return ResponseConversion(request, entity='OK')
@route("/syncboardbyid/<int:boardid>")
@wrapResponse
def syncBoardById(self, request, boardid):
self.core.startSync(boardid)
return ResponseConversion(request, entity='OK')
@route("/pushboardbyid/<int:boardid>")
@wrapResponse
def pushBoardById(self, request, boardid):
self.core.startPush(boardid)
return ResponseConversion(request, entity='OK')
class CronRest(RestCore):
path="timers"
@route("/")
def timerlist(self, request, *a, **kw):
return 'timerlist'
class UserRest(RestCore):
path="users"
@route("/")
@wrapResponse
def userlist(self, request, *a, **kw):
return self.callbackResponse(self.core.getAllUsers(), request, search=True)
@route("/userbyname/<username>/", Http.GET)
@wrapResponse
def userbyname(self, request=None, username='', *a, **kw):
return self.callbackResponse(self.core.getUserFromName(username), request)
@route("/refreshme")
@wrapResponse
def refreshme(self, request=None,*a, **kw):
def setUserSession(res):
self.session.mind.perms.gui_theme=res.gui_theme
self.session.mind.perms.email=res.email
self.session.mind.perms.tts=res.tts
self.session.mind.perms.language=res.language
self.session.mind.perms.slide=res.slide
self.session.mind.perms.webspeech=res.webspeech
self.session.mind.perms.speechlang=res.speechlang
self.session.mind.perms.left_bar=res.left_bar
self.session.mind.perms.right_bar=res.right_bar
return res
log.info('Refresh session for user '+str(self.session.mind.perms.username))
d=self.core.getUserFromName(self.session.mind.perms.username).addCallback(setUserSession)
return self.callbackResponse(d, request)
@route("/me")
@wrapResponse
def getme(self, request=None,*a, **kw):
return self.callbackResponse(self.core.getUserFromName(self.session.mind.perms.username), request)
@route("/me", Http.PUT)
@wrapResponse
def setme(self, request=None,*a, **kw):
def onOk(res):
log.info(res)
return self.callbackResponse(self.core.getUserFromName(self.session.mind.perms.username), request)
def onError(res):
log.info(res)
return ResponseConversion(request, code=404, entity="User not found")
log.info("REST Update user "+str(self.session.mind.perms.username))
r = self._getRequestArgs(request)
pwd=False
tts=False
lang="it"
slide=False
webspeech="touch"
speechlang="it-IT"
theme='dmblack'
leftb='hidden-sm'
rightb='hidden-sm'
if 'lang' in r.keys():
lang=r['lang']
if 'tts' in r.keys():
tts=True
if 'passwd' in r.keys() and r['passwd'] != "":
pwd=r['passwd']
if 'slide' in r.keys():
slide=True
if 'gui_theme' in r.keys():
theme=str(r['gui_theme'])
if 'webspeech' in r.keys() and r['webspeech'] in ['no','touch','continuous']:
webspeech=r['webspeech']
if 'speechlang' in r.keys() and r['speechlang'] in ['it-IT','it-CH','en-US','en-GB']:
speechlang=r['speechlang']
if 'leftb' in r.keys() and r['leftb'] in ['all','none','visible-sm','visible-md','visible-lg','hidden-sm','hidden-md','hidden-lg']:
leftb=str(r['leftb'])
if 'rightb' in r.keys() and r['rightb'] in ['all','none','visible-sm','visible-md','visible-lg','hidden-sm','hidden-md','hidden-lg']:
rightb=str(r['rightb'])
if 'desktop_homepath' in r.keys() and 'mobile_homepath' in r.keys() and 'email' in r.keys():
return self.core.updateUserData(self.session.mind.perms.username, pwd,
r['email'], r['desktop_homepath'], r['mobile_homepath'],
tts, lang, slide, webspeech, speechlang, theme, leftb, rightb).addCallbacks(onOk, onError)
log.info('Erroneous request on update my userdata! ('+str(self.session.mind.perms.username)+')')
return ResponseConversion(request, code=400, entity="Bad request - error in parameters")
class ActionRest(RestCore):
path="actions"
@route("/speech_text", Http.POST)
@wrapResponse
def speechText(self, request, *a, **kw):
r = self._getRequestArgs(request)
confidence = 1.0
if 'confidence' in r.keys():
confidence = float(r['confidence'])
if 'text' in r.keys():
return self.core.voiceReceived(r['text'], confidence).addCallback(
lambda res: res)
return ResponseConversion(request, code=500, entity="No text in request")
@route("/setbyid/<int:aid>",(Http.GET,Http.POST))
@wrapResponse
def setById(self, request, aid, *a, **kw):
return self.callbackResponse(self.core.setActionById(aid), request)
class NotifyRest(RestCore):
path="notifications"
@route("/", Http.GET)
@wrapResponse
def notificationList(self, request, *a, **kw):
return self.callbackResponse(self.core.getNotifications(self.session.mind.perms.username), request, search=True)
@route("/", Http.DELETE)
@wrapResponse
def markAllRead(self, request, *a, **kw):
return self.callbackResponse(self.core.markReadNotifications(self.session.mind.perms.username, '*'), request, search=False)
@route("/<int:nid>", Http.DELETE)
@wrapResponse
def markRead(self, request, nid=0, *a, **kw):
return self.positiveCallback(self.core.markReadNotifications(self.session.mind.perms.username, nid), request, search=False)
@route("/after/<float:fromts>",Http.GET)
@wrapResponse
def notificationsFromTime(self, request, fromts=0, *a, **kw):
return self.callbackResponse(self.core.getNotifications(self.session.mind.perms.username, fromts), request, search=True)
@route("/count",Http.GET)
@wrapResponse
def notificationCount(self, request, *a, **kw):
return self.callbackResponse(self.core.getNotifications(self.session.mind.perms.username, usecount=True), request)
@route("/after/<float:fromts>/count",Http.GET)
@wrapResponse
def notificationsFromTimeCount(self, request, fromts=0, *a, **kw):
return self.callbackResponse(self.core.getNotifications(self.session.mind.perms.username, fromts, usecount=True), request)
class RelayRest(RestCore):
path="relays"
@route("/setbyid/<int:rid>",(Http.GET,Http.POST))
@wrapResponse
def setById(self, request, rid, *a, **kw):
return self.callbackResponse(self.core.setRelayById(rid), request)
@route("/setbyid/<int:rid>/on",(Http.GET,Http.POST))
@wrapResponse
def setOnById(self, request, rid, *a, **kw):
return self.callbackResponse(self.core.setRelayById(rid, 'on'), request)
@route("/setbyid/<int:rid>/off",(Http.GET,Http.POST))
@wrapResponse
def setOffById(self, request, rid, *a, **kw):
return self.callbackResponse(self.core.setRelayById(rid, 'off'), request)
@route("/setbyid/<int:rid>/change",(Http.GET,Http.POST))
@wrapResponse
def setChangeById(self, request, rid, *a, **kw):
return self.callbackResponse(self.core.setRelayById(rid, 'change'), request)
class ChartRest(RestCore):
path="charts"
@route("/", (Http.GET))
@wrapResponse
def charts(self, request, *a, **kw):
return ResponseConversion(request, code=404, entity="Not yet implemented")
@route("/chartbyid/<int:cid>", (Http.GET))
@wrapResponse
def chartById(self, request, cid, *a, **kw):
return ResponseConversion(request, code=404, entity="Not yet implemented (chart by id)")
@route("/chartbyname/<chartname>", (Http.GET))
@wrapResponse
def chartByName(self, request, chartname, *a, **kw):
return self.callbackResponse(self.core.getChartData(chartname), request)
class ClimaRest(RestCore):
path="clima"
@route("/status",(Http.GET))
@wrapResponse
def getStatus(self, request, *a, **kw):
return self.callbackResponse(self.core.getClimaStatus(), request)
@route("/status",(Http.PUT,Http.POST))
@wrapResponse
def setStatus(self, request, *a, **kw):
r = self._getRequestArgs(request)
if 'status' in r.keys():
statusname=r['status']
return self.callbackResponse(self.core.setClimaStatus(statusname), request)
return ResponseConversion(request, code=500, entity="No status in request")
@route("/thermostat/<thermostat>",(Http.GET))
@wrapResponse
def getThermostatStatus(self, request, thermostat, *a, **kw):
return self.callbackResponse(self.core.getThermostat(thermostat), request)
@route("/thermostat/<thermostat>",(Http.PUT,Http.POST))
@wrapResponse
def setThermostat(self, request, thermostat, *a, **kw):
r = self._getRequestArgs(request)
func=False
if 'function' in r.keys() and r['function'] in ['manual','program']:
func=r['function']
setval=False
if 'set' in r.keys() and genutils.is_number(r['set']):
setval=r['set']
return self.callbackResponse(self.core.setThermostat(thermostat, func, setval), request)
@route("/program/<thermostat>/<climastatus>",(Http.GET))
@wrapResponse
def getProgram(self, request, thermostat, climastatus, *a, **kw):
return self.callbackResponse(self.core.getThermostatProgram(thermostat, climastatus), request)
@route("/program/<thermostat>/<climastatus>",(Http.PUT,Http.POST))
@wrapResponse
def setProgram(self, request, thermostat, climastatus, *a, **kw):
r = self._getRequestArgs(request)
return self.callbackResponse(self.core.setThermostatProgram(thermostat, climastatus, r), request)
RESTLIST=(
UserRest,
CronRest,
BoardRest,
BaseRest,
ActionRest,
NotifyRest,
RelayRest,
ChartRest,
ClimaRest,
)
class RestPages(rend.Page):
def child_(self, ctx):
request = inevow.IRequest(ctx)
request.setHeader("pragma", "no-cache")
request.postpath=['/']
return RESTResource((BaseRest(self.core, self.session),))
def childFactory(self, ctx, name):
request = inevow.IRequest(ctx)
request.setHeader("pragma", "no-cache")
request.postpath=['/',name]+request.postpath
return RESTResource([x(self.core, self.session) for x in RESTLIST])
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from twisted.web import server, resource, static, client
from nexlibs import nexcrypt as dmcrypt
try:
from twisted.web import http
except ImportError:
from twisted.protocols import http
import random
import time
try:
import hashlib
md5 = hashlib
md5.new = hashlib.md5
sha1 = hashlib.sha1
except:
import md5
import sha1
import urllib
import logging
from nexlibs.utils.blockingdefer import blockingDeferred
from nexlibs.utils import genutils
import cgi, logging
from zope.interface import implements
from twisted.cred import portal, checkers, credentials
from nevow import inevow, rend, tags, loaders, flat, stan, guard
from nevow import static as nstatic
import proxy, mediaproxy, rest, bot
import os, sys
from twisted.python import reflect
from twisted import cred
from twisted.internet import defer
from nevow import appserver
import time
import auth
from nexlibs.utils.genutils import configFile
import phpserialize
log = logging.getLogger( 'Webgui' )
curdir=os.path.abspath(os.path.dirname(sys.argv[0]))
LOGINFILE=os.path.normpath("/".join([curdir, 'Web/resources/login.html']))
from common import uni, GzipRequest, StaticFile, codeOk, permissionDenied, RedirectToHome, PHPRunner, neededPermission
import ajax
import sse
WEB_SYSTEM_PATHS=[
'/',
'/__LOGOUT__/',
'/__logout__/',
'/__LOGOUT__',
'/__logout__',
'/__LOGIN__/',
'/__login__/',
'/__LOGIN__',
'/__login__',
'/favicon.ico',
]
server.version='DomotikaWeb/1.0'
class RootPage(rend.Page):
addSlash = True
logged = False
childvars = {}
isLeaf = False
perms = False
sse = False
child_resources = StaticFile(curdir+'/Web/resources/')
child_download = StaticFile(curdir+'/Web/download/')
def __init__(self, avatarId=None, port=80):
self.port = port
log.debug("Root page initialized by " + str(avatarId))
super(RootPage, self).__init__(self)
self.avatarId=avatarId
self.putChild('favicon.ico', static.File(curdir+'/Web/resources/img/favicon.ico'))
self.putChild('crossdomain.xml', static.File(curdir+'/Web/resources/xml/crossdomain.xml'))
def renderHTML(self, ctx):
request = inevow.IRequest(ctx)
session = inevow.ISession(ctx)
return rend.Page.renderHTTP(self, ctx)
def child_botauth(self, ctx):
session = inevow.ISession(ctx)
log.debug("BOT AUTH callback received")
botauth = bot.BotAuth()
botauth.core = self.core
return botauth
def child_rest(self, ctx):
if str(self.core.configGet('web', 'enablerestgui')).lower() in ['yes', '1', 'y','true']:
self.rest = rest.RestPages()
self.rest.core = self.core
return self.rest
return self.childFactory(ctx, 'rest')
def child_sse(self, ctx):
if str(self.core.configGet('web', 'enableajaxgui')).lower() in ['yes', '1', 'y','true']:
session = inevow.ISession(ctx)
if not 'sse' in dir(session) or session.sse == False:
session.sse = sse.SseResource(self.core, session.mind.perms.username)
return session.sse
return self.childFactory(ctx, 'sse')
def child_rawplugin(self, ctx):
request = inevow.IRequest(ctx)
pl=request.path.split("/")
if len(pl)>2:
pname=pl[2]
pconf=os.path.normpath("/".join([curdir, 'plugins', pname, 'conf', pname+".conf" ]))
log.debug("trying to read "+str(dconf))
if os.path.isfile(dconf):
try:
pcfg=configFile(pconf)
pcfg.readConfig()
port=int(pcfg.get('web','port'))
except:
port=False
log.debug("Cannot read config file for plugin "+pname)
if port:
self._sendProxySession(request, ctx)
log.debug("Proxying to plugin path "+str(request.path))
return proxy.WebProxyResource('localhost', port, path='/', remove=1)
else:
log.debug("Plugin hasn't a conf file to read")
else:
log.debug("no plugin name in request")
return self.childFactory(ctx, 'rawplugin')
def child_rawdaemon(self, ctx):
request = inevow.IRequest(ctx)
log.debug("Raw Daemon request for "+str(request.path))
pl=request.path.split("/")
if len(pl)>2:
dname=pl[2]
dconf=os.path.normpath("/".join([curdir, 'daemons', dname, 'conf', dname+".conf" ]))
log.debug("trying to read "+str(dconf))
if os.path.isfile(dconf):
try:
dcfg=configFile(dconf)
dcfg.readConfig()
port=int(dcfg.get('web','port'))
except:
port=False
log.debug("Cannot read config file for daemon "+dname)
if port:
self._sendProxySession(request, ctx)
log.debug("Proxying to daemon path "+str(request.path))
return proxy.WebProxyResource('localhost', port, path='/', remove=1)
else:
log.debug("Daemon hasn't a conf file to read")
else:
log.debug("no daemon name in request")
return self.childFactory(ctx, 'rawdaemon')
def child_mediaproxy(self, ctx):
if str(self.core.configGet('web', 'enablemediagui')).lower() in ['yes', '1', 'y','true']:
self.mediaproxy = mediaproxy.MediaStreamProxy()
self.mediaproxy.core = self.core
return self.mediaproxy
return self.childFactory(ctx, 'mediaproxy')
def child_genproxy(self, ctx):
return proxy.GenericProxy(self.core)
def _addPermissions(self, ctx, name, session, request):
def addPerms(dbres, ctx, name, session, request):
try:
log.info(dbres)
if dbres and len(dbres)>0:
session.dmpermissions[request.path]=dbres[0][0]
if neededPermission(request.method) in session.dmpermissions[request.path]:
log.info("PERMISSION DB OK, USER: "+session.mind.perms.username+" SESSION: "+str(session.uid)) #+" ARGS: "+str(request.args)+" REQ "+str(request))
self.core.updateSession(session.uid, session, self)
return rend.Page.locateChild(self, ctx, name)
except:
try:
log.info("Error getting permission from DB USER: "+session.mind.perms.username+" SESSION: "+str(session.uid)+" ARGS: "+str(request.args)+" REQ "+str(request))
except:
log.info("Error getting permission from DB USER: GUEST SESSION: "+str(session.uid)+" ARGS: "+str(request.args)+" REQ "+str(request))
try:
log.info("PERMISSION DB DENIED, USER: "+session.mind.perms.username+" SESSION: "+str(session.uid)+" ARGS: "+str(request.args)+" REQ "+str(request))
except:
log.info("PERMISSION DB DENIED, USER: GUEST SESSION: "+str(session.uid)+" ARGS: "+str(request.args)+" REQ "+str(request))
return permissionDenied(), ()
if not 'sse' in dir(session):
session.sse = False
if not 'dmpermissions' in dir(session):
session.dmpermissions={}
if not request.path in session.dmpermissions.keys():
session.dmpermissions[request.path] = 'none'
try:
log.info("SESS: "+str(session.uid)+" MIND: "+str(session.mind.perms)+" DMPERMS "+str(session.dmpermissions))
return self.core.getPermissionForPath(session.mind.perms.username, request.path).addCallback(
addPerms, ctx, name, session, request
)
except:
log.info("USERS HAS NO MIND??? "+str(request.path)+" "+str(session.uid)+" "+str(request.args)+" REQ "+str(request)+" "+str(session.dmpermissions))
if 'username' in session.dmpermissions.keys():
log.debug("BUT IT HAS DMPERMISSION... "+str(session.dmpermissions))
return self.core.getPermissionForPath(session.dmpermissions['username'], request.path).addCallback(
addPerms, ctx, name, session, request
)
else:
log.debug("ALSO NO USERNAME IN DMPERMISSION "+str(session.uid))
return self.core.getPermissionForPath('guest', request.path).addCallback(
addPerms, ctx, name, session, request
)
def locateChild(self, ctx, name):
session = inevow.ISession(ctx)
request = inevow.IRequest(ctx)
try:
uname = session.mind.perms.username
except:
uname = 'guest'
if not 'sse' in dir(session):
session.sse = False
if not 'dmpermissions' in dir(session):
session.dmpermissions={}
if request.path in WEB_SYSTEM_PATHS:
log.info("WEB_SYSTEM_PATH: USER: "+uname+" SESSION: "+str(session.uid)) #+" ARGS: "+str(request.args)+" REQ "+str(request))
return rend.Page.locateChild(self, ctx, name)
if request.path in session.dmpermissions.keys():
if neededPermission(request.method) in session.dmpermissions[request.path]:
log.debug("PERMISSION OK, SESSION: "+str(session.uid)) #+" ARGS: "+str(request.args)+" REQ "+str(request))
self.core.updateSession(session.uid, session, self)
return rend.Page.locateChild(self, ctx, name)
else:
return self._addPermissions(ctx, name, session, request)
log.debug("PERMISSION DENIED, SESSION: "+str(session.uid)+" ARGS: "+str(request.args)+" REQ "+str(request))
#return rend.Page.locateChild(self, ctx, name)
return permissionDenied(), ()
def _sendProxySession(self, req, ctx):
session = inevow.ISession(ctx)
headers=req.requestHeaders
if headers.hasHeader("DMSESSION"):
headers.removeHeader("DMSESSION")
headervalue = str(session.uid)
cols=['username','passwd','id','homepath','email','tts','language','slide',
'webspeech','speechlang','gui_theme', 'left_bar','right_bar']
try:
headervalue = session.mind.perms.toHash(cols)
except:
headervalue={}
headervalue['username'] = "guest"
headervalue['passwd'] = ""
headervalue['id'] = 0
headervalue['homepath'] = '/'
headervalue['email'] = ""
headervalue['tts']= 0
headervalue['language']="it"
headervalue['slide'] = 0
headervalue['webspeech'] = 'touch'
headervalue['speechlang'] = 'it-IT'
headervalue['gui_theme' ] = 'dmblack'
headervalue['left_bar' ] = 'hidden-sm'
headervalue['right_bar' ] = 'hidden-sm'
headervalue['sessionid'] = session.uid
headervalue['logged'] = self.logged
log.debug('DMSESSION SEND '+str(headervalue))
headers.addRawHeader("DMSESSION", phpserialize.dumps(headervalue))
return req
def child_(self, ctx):
if str(self.core.configGet('web', 'enableusergui')).lower() not in ['yes', '1', 'y','true']:
return "Permission Denied"
html = """
Redirecting...
"""
request = inevow.IRequest(ctx)
host=request.getHeader('host')
log.debug("HOST CALLED: "+str(host))
#log.info("Request: "+str(request))
if host and host in self.core.configGet('proxy', 'localproxyhosts').split(','):
self._sendProxySession(request, ctx)
return proxy.WebProxyResource('localhost', int(self.core.configGet('proxy', 'localproxyport')), path='/')
else:
if self.logged:
if(len(self.perms.homepath)) > 0:
request.setHeader('Location', self.perms.homepath);
else:
request.setHeader('Location', self.core.configGet('web', 'defaultpath'))
else:
request.setHeader('Location', self.core.configGet('web', 'defaultpath'))
request.setResponseCode(302)
return html
def childFactory(self, ctx, name):
#log.info("childFactory "+str(name))
request = inevow.IRequest(ctx)
#log.info("childFactory2 "+str(request))
if name in self.core.configGet('proxy', 'localproxypaths').split(','):
self._sendProxySession(request, ctx)
return proxy.WebProxyResource('localhost', 80, path='/'+name)
host=request.getHeader('host')
log.debug("HOST CALLED: "+str(host))
if host and host in self.core.configGet('proxy', 'localproxyhosts').split(','):
self._sendProxySession(request, ctx)
return proxy.WebProxyResource('localhost', 80, path='/'+name)
log.debug("No child found (%s)" % name)
return permissionDenied()
### Authentication
from twisted.cred.error import UnauthorizedLogin
from nevow import url
class SessionWrapper(guard.SessionWrapper):
def checkLogin(self, ctx, session, segments, *a, **kw):
try:
session.saveargs = session.args
except:
pass
request = inevow.IRequest(ctx)
return guard.SessionWrapper.checkLogin(self, ctx, session, segments, *a, **kw)
def incorrectLoginError(self, error, ctx, segments, loginFailure):
""" Used as an errback upon failed login, returns a 2-tuple of a failure URL
with the query argument 'login-failure' set to the parameter
loginFailure, and an empty list of segments, to redirect to that URL.
The basis for this error URL, i.e. the part before the query string, is
taken either from the 'referer' header from the given request if one
exists, or a computed URL that points at the same page that the user is
currently looking at to attempt login. Any existing query string will
be stripped.
"""
request = inevow.IRequest(ctx)
session = inevow.ISession(ctx)
error.trap(UnauthorizedLogin)
referer = request.getHeader("referer")
if 'botauth' in segments and len(segments) is 3:
segments = ('bot', segments[1], 'loginfailed', '')
u = guard.urlToChild(ctx, *segments)
for k in session.saveargs.keys():
if k not in ['username', 'password'] and session.saveargs[k][0] not in ['Login']:
u = u.add(k, session.saveargs[k][0])
else:
if referer is not None:
u = url.URL.fromString(referer)
else:
u = guard.urlToChild(ctx, *segments)
u = u.clear()
u = u.add('login-failure', loginFailure)
return u, ()
def renderHTTP( self, ctx):
request = inevow.IRequest(ctx)
host=request.getHeader('host')
log.info("USERNAME: "+str(request.getUser())+" "+str(request.getPassword()))
log.debug("SessionWrapper HOST CALLED: "+str(host))
if host and host in self.core.configGet('proxy', 'localproxyhostsnologin').split(','):
log.debug("Proxy Bypass Host in SessionWrapper renderHTTP "+host)
return proxy.WebProxyResource('localhost', int(self.core.configGet('proxy', 'localproxyport')), path='/')
return guard.SessionWrapper.renderHTTP(self, ctx)
def locateChild(self, ctx, segments):
request = inevow.IRequest(ctx)
name = "/".join(segments)
if name=='':
name="/"
log.info("SessionWrapper locateChild "+str(name)+" from IP:"+str(request.getClientIP()))
if name:
if name.startswith('mediaproxy') and request.getClientIP()=='127.0.0.1':
mp = mediaproxy.MediaStreamProxy()
mp.core = self.core
return (mp, segments[1:])
if name.startswith('bot/'):
# Bypass for chat bots
chatbot = bot.BotPages()
chatbot.core = self.core
return (chatbot, segments[1:])
for n in self.core.configGet('proxy', 'localproxypathsnologin').split(','):
if n and name.startswith(n):
log.info("Proxy Bypass localproxypathsnologin locateChild "+name)
return (proxy.WebProxyResource('localhost', 80, path='/'+name), '')
for n in self.core.configGet('web', 'nologinpaths').split(','):
if n and name.startswith(n):
if not ((name==n and n.endswith("/")) or (name[:-1]==n and name.endswith("/"))):
log.debug("Nologin path "+str(name))
return (StaticFile(curdir+'/Web/'+name), '')
host=request.getHeader('host')
log.debug("SessionWrapper HOST CALLED: "+str(host))
for n in self.core.configGet('proxy', 'localproxyhostsnologin').split(','):
if n and host==n:
log.info("Proxy Bypass Host in SessionWrapper locateChild "+host)
return (proxy.WebProxyResource('localhost', 80, path='/'+name), '')
u = self.core.configGet('web', 'nologindefaultuser')
p = self.core.configGet('web', 'nologindefaultpass')
for n in self.core.configGet('web', 'nologinips').split(','):
if ':' in n:
nn = n.split(':')
n = nn[0]
if len(nn) > 1:
u = nn[1]
if len(nn) > 2:
p = nn[2]
if n and n!='no' and n!='':
if genutils.isIp(n):
if n==request.getClientIP():
log.info("IP "+str(request.getClientIP())+" permitted with user "+str(u)+" checking auth...")
request.args["username"] = [u]
request.args["password"] = [p]
request.getUser = lambda: u
request.getPassword = lambda: p
break
if request.getUser() and request.getPassword():
log.info("BASIC AUTH REQUESTED FOR USER "+str(request.getUser()))
request.args["username"] = [request.getUser()]
request.args["password"] = [request.getPassword()]
log.info("Calling Guard..."+str(request.args))
return guard.SessionWrapper.locateChild(self, ctx, segments)
def noLogout():
return None
class LogoutPage(rend.Page):
addSlash = True
def __init__(self, locpath):
self.locpath=locpath.replace("/__LOGOUT__/","/__logout__/")
def renderHTTP(self, ctx):
request = inevow.IRequest(ctx)
rmec=request.getCookie("Domotikad_rme")
# XXX Come si fa a capire SE esiste? ritorna davvero None?
if rmec:
request.addCookie("Domotikad_rme", str(rmec), path="/", secure=True,
expires=http.datetimeToString(time.time()))
request.setHeader('Location', self.locpath)
request.setResponseCode(302)
log.debug("SPECIAL LOGOUT "+self.locpath)
return 'Logging out...'
def childFactory(self, ctx, data):
return self
class RootAuthPage(RootPage):
def __init__(self, avatarId=None, port=80, mind=None):
RootPage.__init__(self, avatarId, port)
self.logged=True
self.mind = mind
self.perms = avatarId[1]
def logout(self):
log.debug("LOGOUT CALLED")
self.logged=False
def locateChild(self, ctx, name):
request = inevow.IRequest(ctx)
if not self.mind.loginsaved:
log.debug("LocateChild in RootAuthPage Ming Args: "+str(self.mind.args))
self.mind.loginsaved = True
self.mind.perms = self.perms
if 'rememberMe' in self.mind.args:
log.debug("Setting rememberMe cookie for avatar "+str(self.avatarId))
#session = inevow.ISession(ctx)
rme=dmcrypt.B64AESEncrypt(str(self.core.configGet('web', 'cookie_aeskey')), self.perms.passwd,
" ".join([self.perms.username, self.perms.loginpwd, self.perms.passwd]))
try:
expire=http.datetimeToString(time.time() + 3600*24*365*50)
except:
expire=http.datetimeToString(time.time() + 3600*24*365*10)
request.addCookie("Domotikad_rme", str(self.perms.id)+':'+rme,
path="/", secure=True, expires=expire)
return RootPage.locateChild(self, ctx, name)
def childFactory(self, ctx, name):
log.debug("RootAuth childFactory")
request = inevow.IRequest(ctx)
if request.path.startswith("/__LOGOUT__/"):
log.debug("__LOGOUT__ CALLED")
return LogoutPage(request.path)
return RootPage.childFactory(self, ctx, name)
class LoginPage(rend.Page):
addSlash = True
html="""<html><head>
<title>Domotika GUI</title>
@SCRIPT@
</head>
<body class="@THEME@">
<form method="post" name="loginform" action="@PATH@">
<fieldset id="form"><legend>Login</legend>
<p>Username: <input type="text" name="username" size="20" value="@USERNAME@" /></p>
<p>Password: <input type="password" name="password" size="20" value="@PASSWORD@" /></p>
<p>Remember me: <input type="checkbox" name="rememberMe" value="Yes" @CHECKED@ /></p>
<p align="center"><input type="submit" value="Login" /></p>
</fieldset>
</form>
</body></html>
"""
child_img = StaticFile(curdir+'/Web/resources/img/')
def __init__(self, *a, **kw):
self.putChild('favicon.ico', static.File(curdir+'/Web/resources/img/favicon.ico'))
if os.path.isfile(LOGINFILE):
try:
lf = open(LOGINFILE, "r")
self.html = lf.read()
lf.close()
except:
pass
rend.Page.__init__(self, *a, **kw)
def renderHTTP( self, ctx):
rme=False
request = inevow.IRequest(ctx)
host=request.getHeader('host')
log.info("LOGIN HOST CALLED: "+str(host))
cookies=request.getHeader('cookie')
if cookies:
cookies=cookies.replace(" ","").split(';')
for cookie in cookies:
cookiename = cookie.split("=")[0]
if cookiename.startswith('Domotikad_session'):
log.info("REMOVE COOKIE: "+str(request.getCookie(cookie.split("=")[0])))
# XXX This won't work as expected if user is logging in with path != from "/"
# Also, is cookie secure even for http requests?
request.addCookie(cookiename, cookie.split("=")[1], expires=http.datetimeToString(time.time()), path="/", secure=True)
elif cookiename.startswith('Domotikad_rme'):
rmec=str(request.getCookie("Domotikad_rme"))
log.info("RememberMe COOKIE FOUND: "+rmec)
rmecl = rmec.split(':')
try:
if len(rmecl) > 1:
uid = str(int(rmecl[0]))
rme = self.core.getUserFromID(uid)
#request.setHeader('Location', '/'+self.resolution+'/home')
#request.setResponseCode(302)
except:
pass
if request.path.startswith("/rest/") and '/keepalive' in request.path:
request.setHeader('content-type', 'application/json')
return '{"data": "SLOGGEDOUT", "uri": "'+request.path+'", "ts": '+str(time.time())+', "result": "succeed"}'
if not rme:
log.info("LOGIN FORM FOR PATH "+request.uri)
return self.getStandardHTML(request.uri)
else:
log.info("LOGIN FROM COOKIE FOR PATH "+request.uri)
return rme.addCallback(self.rmelogin, request, rmec)
def getStandardHTML(self, path):
log.info(path)
html = self.html.replace("@PATH@", '/__login__'+path)
html = html.replace("@USERNAME@", '')
html = html.replace("@PASSWORD@", '')
html = html.replace("@SCRIPT@", "")
html = html.replace("@THEME@", str(self.core.configGet('web', 'logintheme')))
html = html.replace("@CHECKED@", "")
return html
def getScript(self, path):
return '<script> window.onload=function(){ window.setTimeout(function() {document.loginform.submit();}, 1000); };</script>'
def rmelogin(self, res, req, has):
if res and (('__len__' in dir(res) and len(res) > 0) or res!=None ) and len(has.split(":", 1)) > 1:
if '__len__' in dir(res):
user=res[0]
else:
user=res
rme=dmcrypt.B64AESDecrypt(str(self.core.configGet('web', 'cookie_aeskey')), user.passwd, has.split(":", 1)[1])
if len(rme.split()) == 3:
u, lp, p = rme.split()
if user.username == u and user.passwd == p:
log.info("Cookie login succeed for user "+user.username)
try:
expire=http.datetimeToString(time.time() + 3600*24*365*50)
except:
expire=http.datetimeToString(time.time() + 3600*24*365*10)
req.addCookie("Domotikad_rme", has,
path="/", secure=True, expires=expire)
html = self.html.replace("@PATH@", '/__login__'+req.uri)
html = html.replace("@USERNAME@", str(user.username))
html = html.replace("@PASSWORD@", str(lp))
html = html.replace("@CHECKED@", "checked")
html = html.replace("@SCRIPT@", str(self.getScript(req.path)))
html = html.replace("@THEME@", str(self.core.configGet('web', 'logintheme')))
log.debug("login html")
return html
req.addCookie("Domotikad_rme", has, path="/", secure=True, expires=http.datetimeToString(time.time()))
return self.getStandardHTML(req.path)
def child_bot(self, ctx):
log.debug("BOT callback received")
chatbot = bot.BotPages()
chatbot.core = self.core
return chatbot
def childFactory(self, ctx, name):
log.debug("Login childFactory")
request = inevow.IRequest(ctx)
return self
class DomotikaAuthRealm(object):
"""A simple implementor of cred's IRealm.
For web, this gives us the LoggedIn page.
"""
implements(portal.IRealm)
def __init__(self, port):
self.port = port
def requestAvatar(self, authres, mind, *interfaces):
avatarId = authres
log.debug("Avatar is: "+str(avatarId)+' and mind + interfaces '+str(mind)+' '+str(interfaces))
for iface in interfaces:
if iface is inevow.IResource:
# do web stuff
if avatarId is checkers.ANONYMOUS:
resc = LoginPage()
resc.realm = self
resc.core = self.core
return (inevow.IResource, resc, noLogout)
else:
# Qui dovrebbe arrivare in caso di autenticazione
# avatarId dovrebbe contenere il ritorno al checkauth
resc = RootAuthPage(avatarId, self.port, mind)
resc.realm = self
resc.core = self.core
if str(self.core.configGet('web', 'enableajaxgui')).lower() in ['yes', '1', 'y','true']:
resc.putChild('autobahn', ajax.getAutoBahn(self.core, resc.port))
resc.putChild('sockjs', ajax.getSocketJSResource(self.core))
return (inevow.IResource, resc, resc.logout)
raise NotImplementedError("Can't support that interface.")
def getAuthResource(core):
realm = DomotikaAuthRealm(core.configGet('web', 'sslport'))
realm.core = core
porta = portal.Portal(realm)
mycheck=auth.clientAuth()
mycheck.core = core
porta.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous)
porta.registerChecker(mycheck)
site = SessionWrapper(porta, 'Domotikad_session', mindFactory=auth.mindFactory)
site.core = core
return site
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment