├── __pycache__ └── auth.cpython-37.pyc ├── README.md ├── main.py └── auth.py /__pycache__/auth.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syntax/maven-example/main/__pycache__/auth.cpython-37.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maven-example 2 | misc program to demo saas licensing system in a tech interview 3 | 4 | 5 | note the server the api was hosted on is no longer live 6 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import auth 2 | 3 | license = "pdm5bxim2hwh9hbt" 4 | 5 | def display_menu(): 6 | print("Menu:") 7 | print("1. Add two numbers") 8 | print("2. Quit") 9 | 10 | def add_numbers(): 11 | auth.validateUser(license) 12 | try: 13 | num1 = float(input("Enter the first number: ")) 14 | num2 = float(input("Enter the second number: ")) 15 | result = num1 + num2 16 | print(f"Result: {result}") 17 | except ValueError: 18 | print("Please enter valid numbers.") 19 | 20 | def main(): 21 | while True: 22 | display_menu() 23 | choice = input("Enter your choice (1 or 2): ") 24 | 25 | if choice == "1": 26 | add_numbers() 27 | elif choice == "2": 28 | print("Goodbye!") 29 | break 30 | else: 31 | print("Invalid choice. Please enter 1 or 2.") 32 | 33 | 34 | if __name__=='__main__': 35 | auth.validateUser(license) 36 | main() 37 | 38 | -------------------------------------------------------------------------------- /auth.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from uuid import getnode as get_mac 3 | import socket 4 | import platform 5 | 6 | API_KEY = 'test' 7 | 8 | 9 | # Authentication class acts as a framework for building your authnetication system off 10 | # this is a guide and does not to be adheard to exactly, the API functionality should allow for multiple authentication 11 | # flows to exist, whilst all are secure and suffice. 12 | 13 | class Authentication(): 14 | def __init__(self, licenseid): 15 | self.license = licenseid 16 | self.hwid = None 17 | self.devicename = None 18 | self.isBoundToUser = False 19 | 20 | self.headers = {'api_key': API_KEY} 21 | 22 | self.getLicenseInfo() 23 | 24 | def getLicenseInfo(self): 25 | # resp = requests.get(f'http://161.35.164.117:5000/api/v1/licenses/{self.license}', headers=self.headers) 26 | # print(resp) 27 | # performs GET request given a license key and attempts to return its attributes 28 | try: 29 | resp = requests.get(f'http://161.35.164.117:5000/api/v1/licenses/{self.license}', headers=self.headers).json() 30 | self.hwid = resp['license']['HWID'] 31 | self.devicename = resp['license']["device"] 32 | self.isBoundToUser = bool(resp['license']["boundToUser"]) 33 | return resp['license'] 34 | except: 35 | self.license = None 36 | return None 37 | 38 | def setToBound(self, hwid, devicename): 39 | # performs POST request given a license key sets it to bound, given appropriate request data 40 | payloadjson = { 41 | "HWID": f"{hwid}", 42 | "device": f"{devicename}" 43 | } 44 | resp = requests.post(f'http://161.35.164.117:5000/api/v1/licenses/{self.license}', headers=self.headers, 45 | json=payloadjson).json() 46 | return resp 47 | 48 | def setToUnbound(self): 49 | # performs POST request given a license key sets it to unbound 50 | payloadjson = { 51 | "HWID": None, 52 | "device": None 53 | } 54 | resp = requests.post(f'http://161.35.164.117:5000/api/v1/licenses/{self.license}', headers=self.headers, 55 | json=payloadjson).json() 56 | return resp 57 | 58 | 59 | # Local functions (to follow) should be built in whatever way the developer sees fit, i.e. should derive the "HWID" element in a unique way, not necessarily the example shown. 60 | # more device related data can be collected using external libararys like psutil, which can be isntalled via pip, however for the sake of example the libraries used are preinstalled with py 61 | # These functions should be implemented around the developers software they are wanting to distrubute in order to validate users. 62 | 63 | def collectLocalData(): 64 | # this function will end up being called often for comparison, and could be written in a variety of ways 65 | 66 | def deriveHWID(): 67 | # gets device MAC address 68 | mac_address = get_mac() 69 | # gets name of local microprocessor 70 | processor_arch = platform.uname().processor 71 | # gets instruction set architecture 72 | machine = platform.uname().machine 73 | 74 | # any convolution of relevant data would be valid, could be hashed, hashed with a pepper, etc. 75 | # how this value is derived should be kept unkown to the user of the application 76 | return str(mac_address) + processor_arch + machine 77 | 78 | hwid = deriveHWID() 79 | # gets name of local node 80 | devicename = socket.gethostname() 81 | 82 | return hwid, devicename 83 | 84 | 85 | def validateUser(license): 86 | # this function will end uo being called often, ideally at key function within the developers program, and could be written in a variety of ways 87 | # this authenticates a users license to be valid, and not currently 88 | 89 | auth = Authentication(license) 90 | localhwid, localdevname = collectLocalData() 91 | if auth.license and auth.isBoundToUser: 92 | # checks license key is still valid 93 | if not (auth.hwid and auth.devicename): 94 | # in the case where license is currently unbound 95 | auth.setToBound(localhwid, localdevname) 96 | else: 97 | if auth.hwid == localhwid and auth.devicename == localdevname: 98 | # proceed with operation, license still valid 99 | pass 100 | else: 101 | # license is bound to another machine, not the one it is attempting to be used on, hence quit program 102 | print('ERORR: license already bound to another machine, quiting') 103 | quit() 104 | else: 105 | # license key is invalid, hence quit progam 106 | print('ERROR: license is invalid, or not bound to a user, quiting') 107 | quit() 108 | --------------------------------------------------------------------------------