#!/usr/bin/env python # The Edge sign in system, RESTful/json backend, v.0.7 # Copyright Byron Crowe, 2016. # This program is distributed under the terms of the GNU General Public License import os import sys import nfc import time import json import web import threading import hashlib #parent_id = linked card ## ## Load user database in. Returns a dict. Currenly loads JSON ## def loadusers(): print "Loading database:", with open("cards.json","r") as f: userdict=json.load(f) print " Loaded:",len(userdict.keys()) return userdict ## ## Save user database. Expects a dict. Currenly saves as JSON ## def saveusers(userdict): print "Cleaning database:", for test in userdict: # print "Loaded:",test," >",userdict[test].get('flags') # userdict[test]['flags']="novalid,noexpire" for cleanup in userdict[test].keys(): if userdict[test].get(cleanup)==None: print "Already cleared:"+test+" key:"+cleanup, elif userdict[test].get(cleanup)=="": print "Deleing:"+test+" key:"+cleanup, del userdict[test][cleanup] print "." print "Saving database", # return # Uncomment to disable saving with open("cards.json","w") as f: json.dump(cardusers,f,sort_keys = True, indent = 4, ensure_ascii = False) print "." ## ## Update card/tag data. Do validity checks and only update passed data. ## Also create new card/tag if none exists ## def updatecarddata(updatedata): global cardusers returnvalue = "Tag updated." # Default message, we'll pike otherwise # Handle things that will cause an update to fail if currentcard==None: return "Error: No tag!" if updatedata.get('cardid') != currentcard: return "Error: Tag removed, data not updated!" if cardusers.get(currentcard)==None: print "Adding tag:"+currentcard returnvalue="New tag! Adding to database." cardusers[currentcard] = { 'name': '-none-', 'last_seen': '' } else: print "Updating tag:"+currentcard del updatedata['cardid'] # This should not end up in the database for updatekey in updatedata: if cardusers[currentcard].get(updatekey)!=None: print "Updating>"+updatekey+"<>"+updatedata[updatekey]+"< ", cardusers[currentcard][updatekey]=updatedata[updatekey] else: if updatedata[updatekey]: print "Adding>"+updatekey+"<>"+updatedata[updatekey]+"< ", cardusers[currentcard][updatekey]=updatedata[updatekey] else: print "Not using>"+updatekey+"< ", print saveusers(cardusers) return returnvalue ## ## Sign in user, save data to both main log and emergancy log. ## Then if the card is still on the reader, trigger an update ## def signinuser(updatedata): signincard = updatedata['cardid'] signinfile = 'signindata.csv' # This file should be unique for any one date/day. emergfile = hashlib.md5(`time.strftime("%a, %d %b %Y")+'Salt'`).hexdigest()[:10]+".txt" # if not cardusers.get('emergfile'): # cardusers['emergfile']= # String for signin 'sheet' s=`time.strftime("%a, %d %b %Y %H:%M:%S")`+',"' s=s+updatedata['name']+'","'+updatedata['email']+'","'+updatedata.get('visit_use',"None")+'",' # Update vists if user has a tag, othersise give a '-' if(cardusers.get(signincard)): cardusers[signincard]['visits'] = cardusers[signincard].get('visits',0) + 1 s=s+`cardusers[signincard]['visits']`+"\n" else: s=s+"-\n" #String for emergancy log e='Signed in:'+`time.strftime("%H:%M:%S")`+' Name:'+updatedata['name']+' Phone:'+updatedata['phone']+' Visit reason:'+updatedata.get('visit_use',"None") e=e+'\nEmergancy details. Name:'+updatedata['e_name']+' Phone:'+updatedata['e_phone']+' Relation/notes:'+updatedata['e_relation'] e=e+'\n\n' # Make sure we have a file, if not, give us some headers. if(os.path.isfile(signinfile) != True): sf = open(emergfile, 'w') sf.write("Time,name,email,reasion,vists\n") sf.close() # Same for emergancy sheet, title with today's date. if(os.path.isfile(emergfile) != True): ef = open(emergfile, 'w') ef.write(" Emergancy details file for:"+`time.strftime("%a, %d %b %Y")`+"\n\n") ef.close() # Open, write out and close signin/emerg files. sf = open(signinfile, 'a') ef = open(emergfile, 'a') print "Logged data:"+s sf.write(s) ef.write(e) sf.close() ef.close() # User didn't put a card on the reader or removed it before this call if currentcard==None: return "Signed in!\n(No tag/update)" # Ok! Save any changes. return 'Signed in!\n'+updatecarddata(updatedata) ## ## Delete a tag/card, do finial checks first. ## def deletecarddata(updatedata): # User didn't put a card on the reader or removed it before this call if currentcard==None: return "Error: No Tag!" if updatedata.get('cardid') != currentcard: return "Error: Tag Changed!" # Delete tag print "Deleting tag:"+currentcard del cardusers[currentcard] # Actual delete saveusers(cardusers) return "Tag deleted from database." ## ## --- Web servery stuff -- ## class MyWebserver(threading.Thread): # Start the web server. def run (self): urls = ( '/card/id', 'web_get_id', '/card/data', 'web_get_carduser', '/card/update', 'web_card_update' ) app = web.application(urls, globals()) print "Starting:", app.run() print "Started." def stop(self): print "Stopping web server" os._exit(0) # self.process.terminate() # self.process = None # Return just if there is a card on the reader and card id. class web_get_id: def GET(self): web.header('Access-Control-Allow-Origin', '*') web.header('Access-Control-Allow-Method', '*') web.header('Access-Control-Allow-Credentials', 'true') return json.dumps({ 'cardid': currentcard }) # Return full user data for current card # This will eventually involve a database lookup and other things so is a diffrent call to just 'tag on reader'. class web_get_carduser: def GET(self): web.header('Access-Control-Allow-Origin', '*') web.header('Access-Control-Allow-Method', '*') web.header('Access-Control-Allow-Credentials', 'true') # If current card on the reader is an emergancy card, give the current emergfilename. if cardusers.get(currentcard): if cardusers[currentcard].get('to_use')=='emerg,emerg,emerg': cardusers[currentcard]['notes']=hashlib.md5(`time.strftime("%a, %d %b %Y")+'Salt'`).hexdigest()[:10]+".txt" return json.dumps({ 'cardid': currentcard, "Data":cardusers.get(currentcard, 'No User') }) # Main return/update/signin function. Looks after: Normal and advanced 'update's, 'signin' and 'delete' # Most actions result in a 'return' before the end of the function. class web_card_update: def POST(self): web.header('Access-Control-Allow-Origin', '*') web.header('Access-Control-Allow-Method', '*') web.header('Access-Control-Allow-Credentials', 'true') # data = web.data() # print "Post got ->",data,"<-" webdata=web.input(); #dvanced_="false",submitmode_="" # Clean up import data. Put in a new dict for later as web.import dosen't surport .get() # All returned fields without data get a "", maybe they should be not added to the dict? safedata={} for webkeys in webdata: toclean=web.input(webkeys="")[webkeys] toclean=toclean[:60] # Limit fields to 60 chars. Need exception for 'notes' toclean = toclean.replace('"',"'"); # Any other 'replace' cleanups need to be here. # print "webkey>"+webkeys+"< Data>"+toclean+"<" safedata[webkeys[:-1]]=toclean # Remove trailing _ from returned names to create actual keys. # Return # updatedata=validandtranslate(safedata) # Sign in user. Do first checks, then call function. if(safedata.get("submitmode")=="signin"): updatedata=validandtranslate(safedata,("cardid","name","email","phone","e_name","e_phone","e_relation","notes","visit_use")) print "Log user in!" if not safedata.get('flags').find("novalid"): # We don't want test tags signing in. print " 'novalid' found.." return "Not valid user." if updatedata['visit_use'] == "": # Allow for to_use to overwrite visit_use print "To Use Override:"+updatedata['to_use'] # Might not be needed due to web interface updates. updatedata['visit_use']=updatedata['to_use'] return signinuser(updatedata) # Advanced mode, update 'invalid' cards and enables delete # Also allows update of aditonal fields elif(safedata.get("advanced")=="true"): updatedata=validandtranslate(safedata,("cardid","name","email","phone","to_use","e_name","e_phone","e_relation","notes","visit_use","flags")) print "Advanced mode: Submit:",safedata.get("submitmode")," CardID:",safedata.get("cardid")," Parent:",safedata.get("parentid")," Name:",safedata.get("name") if(safedata.get("submitmode")=="update"): # Update but not signin, some checks disabled # updatedata['parentid']=webdata.parentid_ return updatecarddata(updatedata) # Advanced update function elif(safedata.get("submitmode")=="delete"): # Delete a card, throw to function for final checks. if(safedata.get("delete_sure")=="true"): # print "Delete tag:"+updatedata['cardid'] return deletecarddata(updatedata) else: return "Error: No confirm for delete." # return "Error: Invalid submit request!" def GET(self): return "Nope! Your using the wrong update method. Go away." # Checks and only passes valid keys # Returns empty data field for no data def validandtranslate(indict,validkeys): #"parentid", # Need to enable this # validkeys=("cardid","name","email","phone","to_use","e_name","e_phone","e_relation","notes","visit_use","flags") outdict={} print "Check keys: "+`validkeys` for checkkey in validkeys: if (indict.get(checkkey,None)): print "|"+checkkey+"<>"+indict[checkkey]+"| ", outdict[checkkey]=indict[checkkey] else: print "|"+checkkey+" 1 started = time.time(); clf.connect(rdwr={'on-connect': process_tag_data}, terminate=after1s) # Best way to cleanly kill the whole program due to web/nfc blocking if currenttag == thekilltag: print "Kill tag!("+cardusers[thekilltag]['to_use']+") exiting.." cardusers[currenttag]['last_seen']=time.time() # Save current time of kill shutdown() # print "Current:",currenttag," Prev:",prevtag if prevtag==0 and currenttag==0: print "Waiting for next card." elif currenttag==prevtag: print "Card read, please remove card." # keep this if we want to keep info up while card is held on reader last_tag_read_time = time.time() else: prevtag=currenttag if currenttag != 0: # global declaraion not needed since we're outside a class/function currentcard = currenttag last_tag_read_time = time.time() print 'Tag ID:',currenttag,'User',cardusers.get(currenttag, 'No User') if(cardusers.has_key(currenttag)): cardusers[currenttag]['last_seen']=time.time() # Needs extra bits to handle parent/child cards else: print "Unknown tag." # clear tag data after a timeout if last_tag_read_time + tag_data_timeout < time.time(): last_tag_read_time = time.time() currentcard = None print "Card timeout." if currenttag != 0: # Bit of a delay if a card is on time.sleep(0.5) # This blocks, so maybe a problem?