244 |
245 | This error is thrown when the urllib library (a built in library in Python that we use to send HTTP requests) is unable to locate the CA files on the client machine. From our experience, this error occurs exclusively on Macs where the Python environment is incorrectly installed.
246 |
247 | To solve this temporarily for **testing purposes**, you could temporary disable SSL verifications as described in [here](#ssl-verification), however, we do not recommend this in a production scenario. Instead, a better solution is to fix the underlying issue preventing the Python environment from finding the CA files.
248 |
249 | This can be accomplished in at least two ways:
250 |
251 | ##### Using certifi
252 | Before calling any of the API methods (e.g. Key.activate), you can add the following code:
253 |
254 | ```python
255 | import certifi
256 | os.environ['SSL_CERT_FILE'] = certifi.where()
257 | ```
258 |
259 | Please note that this requires `certifi` package to be installed.
260 |
261 | ##### Running a script in the Python environment
262 | An alternative is to run script in their environment that should fix the issue. You can read more about it in this thread: https://github.com/Cryptolens/cryptolens-python/issues/65
263 |
264 | ##### Summary
265 | The key takeaway is that it is better to address the issue with missing CA on the user side, since this issue will typically be user-specific. If that is not possible, you can use the code above to manually set the path to CA files. Although we have mentioned turning off SSL verification temporarily, it should not be used in production. `Key.activate` takes care of signature verification internally, but some other methods do not.
266 |
267 |
268 |
269 |
270 |
--------------------------------------------------------------------------------
/cryptolens_python2.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import socket
3 | import json
4 |
5 | import os
6 |
7 | """
8 | The code below should not be changed.
9 | """
10 |
11 | # -*- coding: utf-8 -*-
12 | """
13 | Created on Wed Jan 23 10:12:13 2019
14 |
15 | @author: Artem Los
16 | """
17 | import base64
18 | import urllib2
19 | import urllib
20 | import hashlib
21 | from subprocess import Popen, PIPE
22 | from urllib2 import URLError, HTTPError
23 |
24 | class HelperMethods:
25 |
26 | server_address = "https://app.cryptolens.io/api/"
27 | ironpython2730_legacy = False
28 |
29 | @staticmethod
30 | def get_SHA256(string):
31 | """
32 | Compute the SHA256 signature of a string.
33 | """
34 | return hashlib.sha256(string.encode("utf-8")).hexdigest()
35 |
36 | @staticmethod
37 | def I2OSP(x, xLen):
38 | if x > (1 << (8 * xLen)):
39 | return None
40 | Xrev = []
41 | for _ in xrange(0, xLen):
42 | x, m = divmod(x, 256)
43 | Xrev.append(chr(m))
44 | return "".join(reversed(Xrev))
45 |
46 | @staticmethod
47 | def OS2IP(X):
48 | return int(X.encode("hex"), 16)
49 |
50 | @staticmethod
51 | def _OS2IP(X):
52 | x = 0
53 | a = 1
54 | l = len(X)
55 | for i in xrange(1, l+1):
56 | x += ord(X[l - i])*a
57 | a *= 256
58 | return x
59 |
60 | @staticmethod
61 | def RSAVP1((n,e), s):
62 | if s < 0 or n-1 < s:
63 | return None
64 | return pow(s, e, n)
65 |
66 | @staticmethod
67 | def EMSA_PKCS1_V15_ENCODE(M, emLen):
68 | import hashlib
69 | h = hashlib.sha256()
70 | h.update(M)
71 | H = h.digest()
72 |
73 | T = "".join([chr(x) for x in [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20]]) + H
74 | tLen = len(T)
75 | if emLen < tLen + 11:
76 | return None
77 | PS = "".join([chr(0xff) for _ in range(emLen - tLen - 3)])
78 | return "".join([chr(0x0), chr(0x1), PS, chr(0x0), T])
79 |
80 | @staticmethod
81 | def RSAASSA_PKCS1_V15_VERIFY((n,e), M, S):
82 | s = HelperMethods.OS2IP(S)
83 | m = HelperMethods.RSAVP1((n,e), s)
84 | if m is None: return False
85 | EM = HelperMethods.I2OSP(m, 256)
86 | if EM is None: return False
87 | EM2 = HelperMethods.EMSA_PKCS1_V15_ENCODE(M, 256)
88 | if EM2 is None: return False
89 |
90 | try:
91 | import hmac
92 | return hmac.compare_digest(EM, EM2)
93 | except (ImportError, AttributeError):
94 | return EM == EM2
95 |
96 |
97 | @staticmethod
98 | def verify_signature(response, rsaPublicKey):
99 | """
100 | Verifies a signature from .NET RSACryptoServiceProvider.
101 | """
102 |
103 | modulus = base64.b64decode(rsaPublicKey.modulus)
104 | exponent = base64.b64decode(rsaPublicKey.exponent)
105 | message = base64.b64decode(response.license_key)
106 | signature = base64.b64decode(response.signature)
107 |
108 | n = HelperMethods.OS2IP(modulus)
109 | e = HelperMethods.OS2IP(exponent)
110 |
111 | return HelperMethods.RSAASSA_PKCS1_V15_VERIFY((n,e), message, signature)
112 |
113 | @staticmethod
114 | def int2base64(num):
115 | return base64.b64encode(int.to_bytes(num), byteorder='big')
116 |
117 | @staticmethod
118 | def base642int(string):
119 | return int.from_bytes(base64.b64decode((string)), byteorder='big')
120 |
121 | @staticmethod
122 | def send_request(method, params):
123 | """
124 | Send a POST request to method in the Web API with the specified
125 | params and return the response string.
126 |
127 | method: the path of the method, eg. key/activate
128 | params: a dictionary of parameters
129 | """
130 |
131 | if HelperMethods.ironpython2730_legacy:
132 | return HelperMethods.send_request_ironpythonlegacy(HelperMethods.server_address + method, \
133 | urllib.urlencode(params))
134 | else:
135 | return urllib2.urlopen(HelperMethods.server_address + method, \
136 | urllib.urlencode(params)).read().decode("utf-8")
137 |
138 | @staticmethod
139 | def send_request_ironpythonlegacy(uri, parameters):
140 | """
141 | IronPython 2.7.3 and earlier has a built in problem with
142 | urlib2 library when verifying certificates. This code calls a .NET
143 | library instead.
144 | """
145 | from System.Net import WebRequest
146 | from System.IO import StreamReader
147 | from System.Text import Encoding
148 |
149 | request = WebRequest.Create(uri)
150 |
151 | request.ContentType = "application/x-www-form-urlencoded"
152 | request.Method = "POST" #work for post
153 | bytes = Encoding.ASCII.GetBytes(parameters)
154 | request.ContentLength = bytes.Length
155 | reqStream = request.GetRequestStream()
156 | reqStream.Write(bytes, 0, bytes.Length)
157 | reqStream.Close()
158 |
159 | response = request.GetResponse()
160 | result = StreamReader(response.GetResponseStream()).ReadToEnd()
161 | return result
162 |
163 |
164 | @staticmethod
165 | def start_process(command):
166 |
167 | process = Popen(command, stdout=PIPE)
168 | (output, err) = process.communicate()
169 | exit_code = process.wait()
170 | return output.decode("utf-8")
171 |
172 | @staticmethod
173 | def get_dbus_machine_id():
174 | try:
175 | with open("/etc/machine-id") as f:
176 | return f.read().strip()
177 | except:
178 | pass
179 | try:
180 | with open("/var/lib/dbus/machine-id") as f:
181 | return f.read().strip()
182 | except:
183 | pass
184 | return ""
185 |
186 | @staticmethod
187 | def get_inodes():
188 | import os
189 | files = ["/bin", "/etc", "/lib", "/root", "/sbin", "/usr", "/var"]
190 | inodes = []
191 | for file in files:
192 | try:
193 | inodes.append(os.stat(file).st_ino)
194 | except:
195 | pass
196 | return "".join([str(x) for x in inodes])
197 |
198 |
199 | @staticmethod
200 | def compute_machine_code():
201 | return HelperMethods.get_dbus_machine_id() + HelperMethods.get_inodes()
202 |
203 | import platform
204 | import uuid
205 | import sys
206 | import json
207 |
208 | class Key:
209 |
210 | """
211 | License key related methods. More docs: https://app.cryptolens.io/docs/api/v3/Key.
212 | """
213 |
214 | @staticmethod
215 | def activate(token, rsa_pub_key, product_id, key, machine_code, fields_to_return = 0,\
216 | metadata = False, floating_time_interval = 0,\
217 | max_overdraft = 0, friendly_name=None):
218 |
219 | """
220 | Calls the Activate method in Web API 3 and returns a tuple containing
221 | (LicenseKey, Message). If an error occurs, LicenseKey will be None. If
222 | everything went well, no message will be returned.
223 |
224 | More docs: https://app.cryptolens.io/docs/api/v3/Activate
225 | """
226 |
227 | response = Response("","",0,"")
228 |
229 | try:
230 | response = Response.from_string(HelperMethods.send_request("key/activate", {"token":token,\
231 | "ProductId":product_id,\
232 | "key":key,\
233 | "MachineCode":machine_code,\
234 | "FieldsToReturn":fields_to_return,\
235 | "metadata":metadata,\
236 | "FloatingTimeInterval": floating_time_interval,\
237 | "MaxOverdraft": max_overdraft,\
238 | "FriendlyName" : friendly_name,\
239 | "ModelVersion" : 2,\
240 | "Sign":"True",\
241 | "SignMethod":1}))
242 | except HTTPError as e:
243 | response = Response.from_string(e.read())
244 | except URLError as e:
245 | return (None, "Could not contact the server. Error message: " + str(e))
246 | except Exception:
247 | return (None, "Could not contact the server.")
248 |
249 | pubkey = RSAPublicKey.from_string(rsa_pub_key)
250 |
251 | if response.result == 1:
252 | return (None, response.message)
253 | else:
254 | try:
255 | if HelperMethods.verify_signature(response, pubkey):
256 | return (LicenseKey.from_response(response), response.message)
257 | else:
258 | return (None, "The signature check failed.")
259 | except Exception:
260 | return (None, "The signature check failed.")
261 |
262 | @staticmethod
263 | def get_key(token, rsa_pub_key, product_id, key, fields_to_return = 0,\
264 | metadata = False, floating_time_interval = 0):
265 |
266 | """
267 | Calls the GetKey method in Web API 3 and returns a tuple containing
268 | (LicenseKey, Message). If an error occurs, LicenseKey will be None. If
269 | everything went well, no message will be returned.
270 |
271 | More docs: https://app.cryptolens.io/docs/api/v3/GetKey
272 | """
273 |
274 | response = Response("","",0,"")
275 |
276 | try:
277 | response = Response.from_string(HelperMethods.send_request("key/getkey", {"token":token,\
278 | "ProductId":product_id,\
279 | "key":key,\
280 | "FieldsToReturn":fields_to_return,\
281 | "metadata":metadata,\
282 | "FloatingTimeInterval": floating_time_interval,\
283 | "Sign":"True",\
284 | "SignMethod":1}))
285 | except HTTPError as e:
286 | response = Response.from_string(e.read())
287 | except URLError as e:
288 | return (None, "Could not contact the server. Error message: " + str(e))
289 | except Exception:
290 | return (None, "Could not contact the server.")
291 |
292 | pubkey = RSAPublicKey.from_string(rsa_pub_key)
293 |
294 | if response.result == 1:
295 | return (None, response.message)
296 | else:
297 | try:
298 | if HelperMethods.verify_signature(response, pubkey):
299 | return (LicenseKey.from_response(response), response.message)
300 | else:
301 | return (None, "The signature check failed.")
302 | except Exception:
303 | return (None, "The signature check failed.")
304 |
305 | @staticmethod
306 | def create_trial_key(token, product_id, machine_code):
307 | """
308 | Calls the CreateTrialKey method in Web API 3 and returns a tuple containing
309 | (LicenseKeyString, Message). If an error occurs, LicenseKeyString will be None. If
310 | everything went well, no message will be returned.
311 |
312 | More docs: https://app.cryptolens.io/docs/api/v3/CreateTrialKey
313 | """
314 |
315 | response = ""
316 |
317 | try:
318 | response = HelperMethods.send_request("key/createtrialkey", {"token":token,\
319 | "ProductId":product_id,\
320 | "MachineCode":machine_code})
321 | except HTTPError as e:
322 | response = e.read()
323 | except URLError as e:
324 | return (None, "Could not contact the server. Error message: " + str(e))
325 | except Exception:
326 | return (None, "Could not contact the server.")
327 |
328 | jobj = json.loads(response)
329 |
330 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
331 | if jobj != None:
332 | return (None, jobj["message"])
333 | else:
334 | return (None, "Could not contact the server.")
335 |
336 | try:
337 | return (jobj["key"], "")
338 | except:
339 | return (None, "An unexpected error occurred")
340 |
341 | @staticmethod
342 | def deactivate(token, product_id, key, machine_code, floating = False):
343 | """
344 | Calls the Deactivate method in Web API 3 and returns a tuple containing
345 | (Success, Message). If an error occurs, Success will be False. If
346 | everything went well, Sucess is true and no message will be returned.
347 |
348 | More docs: https://app.cryptolens.io/docs/api/v3/Deactivate
349 | """
350 |
351 | response = ""
352 |
353 | try:
354 | response = HelperMethods.send_request("key/deactivate", {"token":token,\
355 | "ProductId":product_id,\
356 | "Key" : key,\
357 | "Floating" : floating,\
358 | "MachineCode":machine_code})
359 | except HTTPError as e:
360 | response = e.read()
361 | except URLError as e:
362 | return (None, "Could not contact the server. Error message: " + str(e))
363 | except Exception:
364 | return (None, "Could not contact the server.")
365 |
366 | jobj = json.loads(response)
367 |
368 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
369 | if jobj != None:
370 | return (False, jobj["message"])
371 | else:
372 | return (False, "Could not contact the server.")
373 |
374 | return (True, "")
375 |
376 |
377 |
378 | class Helpers:
379 |
380 | @staticmethod
381 | def GetMachineCode():
382 |
383 | """
384 | Get a unique identifier for this device.
385 | """
386 |
387 | if "windows" in platform.platform().lower():
388 | return HelperMethods.get_SHA256(HelperMethods.start_process(["cmd.exe", "/C", "wmic","csproduct", "get", "uuid"]))
389 | elif "darwin" in platform.platform().lower():
390 | res = HelperMethods.start_process(["system_profiler","SPHardwareDataType"]).decode('utf-8')
391 | return HelperMethods.get_SHA256(res[res.index("UUID"):].strip())
392 | elif "linux" in platform.platform(HelperMethods.compute_machine_code()):
393 | return HelperMethods.get_SHA256(HelperMethods.compute_machine_code())
394 | else:
395 | return HelperMethods.get_SHA256(HelperMethods.compute_machine_code())
396 |
397 | @staticmethod
398 | def IsOnRightMachine(license_key, is_floating_license = False, allow_overdraft=False, custom_machine_code = None):
399 |
400 | """
401 | Check if the device is registered with the license key.
402 | """
403 |
404 | current_mid = ""
405 |
406 | if custom_machine_code == None:
407 | current_mid = Helpers.GetMachineCode()
408 | else:
409 | current_mid = custom_machine_code
410 |
411 | if license_key.activated_machines == None:
412 | return False
413 |
414 | if is_floating_license:
415 | if len(license_key.activated_machines) == 1 and \
416 | (license_key.activated_machines[0].Mid[9:] == current_mid or \
417 | allow_overdraft and license_key.activated_machines[0].Mid[19:] == current_mid):
418 | return True
419 | else:
420 | for act_machine in license_key.activated_machines:
421 | if current_mid == act_machine.Mid:
422 | return True
423 |
424 | return False
425 |
426 | import xml.etree.ElementTree
427 | import json
428 | import base64
429 | import datetime
430 | import copy
431 | import time
432 |
433 | class ActivatedMachine:
434 | def __init__(self, IP, Mid, Time, FriendlyName = ""):
435 | self.IP = IP
436 | self.Mid = Mid
437 |
438 | # TODO: check if time is int, and convert to datetime in this case.
439 | self.Time = Time
440 | self.FriendlyName = FriendlyName
441 |
442 | class LicenseKey:
443 |
444 | def __init__(self, ProductId, ID, Key, Created, Expires, Period, F1, F2,\
445 | F3, F4, F5, F6, F7, F8, Notes, Block, GlobalId, Customer, \
446 | ActivatedMachines, TrialActivation, MaxNoOfMachines, \
447 | AllowedMachines, DataObjects, SignDate, RawResponse):
448 |
449 | self.product_id = ProductId
450 | self.id = ID
451 | self.key = Key
452 | self.created = Created
453 | self.expires = Expires
454 | self.period = Period
455 | self.f1 = F1
456 | self.f2 = F2
457 | self.f3 = F3
458 | self.f4 = F4
459 | self.f5 = F5
460 | self.f6 = F6
461 | self.f7 = F7
462 | self.f8 = F8
463 | self.notes = Notes
464 | self.block = Block
465 | self.global_id = GlobalId
466 | self.customer = Customer
467 | self.activated_machines = ActivatedMachines
468 | self.trial_activation = TrialActivation
469 | self.max_no_of_machines = MaxNoOfMachines
470 | self.allowed_machines = AllowedMachines
471 | self.data_objects = DataObjects
472 | self.sign_date = SignDate
473 | self.raw_response = RawResponse
474 |
475 | @staticmethod
476 | def from_response(response):
477 |
478 | if response.result == "1":
479 | raise ValueError("The response did not contain any license key object since it was unsuccessful. Message '{0}'.".format(response.message))
480 |
481 | obj = json.loads(base64.b64decode(response.license_key).decode('utf-8'))
482 |
483 | return LicenseKey(obj["ProductId"], obj["ID"], obj["Key"], datetime.datetime.fromtimestamp(obj["Created"]),\
484 | datetime.datetime.fromtimestamp(obj["Expires"]), obj["Period"], obj["F1"], obj["F2"], \
485 | obj["F3"], obj["F4"],obj["F5"],obj["F6"], obj["F7"], \
486 | obj["F8"], obj["Notes"], obj["Block"], obj["GlobalId"],\
487 | obj["Customer"], LicenseKey.__load_activated_machines(obj["ActivatedMachines"]), obj["TrialActivation"], \
488 | obj["MaxNoOfMachines"], obj["AllowedMachines"], obj["DataObjects"], \
489 | datetime.datetime.fromtimestamp(obj["SignDate"]), response)
490 |
491 | def save_as_string(self):
492 | """
493 | Save the license as a string that can later be read by load_from_string.
494 | """
495 | res = copy.copy(self.raw_response.__dict__)
496 | res["licenseKey"] = res["license_key"]
497 | res.pop("license_key", None)
498 | return json.dumps(res)
499 |
500 | @staticmethod
501 | def load_from_string(rsa_pub_key, string, signature_expiration_interval = -1):
502 | """
503 | Loads a license from a string generated by save_as_string.
504 | Note: if an error occurs, None will be returned. An error can occur
505 | if the license string has been tampered with or if the public key is
506 | incorrectly formatted.
507 |
508 | :param signature_expiration_interval: If the license key was signed,
509 | this method will check so that no more than "signatureExpirationInterval"
510 | days have passed since the last activation.
511 | """
512 |
513 | response = Response("","","","")
514 |
515 | try:
516 | response = Response.from_string(string)
517 | except Exception as ex:
518 | return None
519 |
520 | if response.result == "1":
521 | return None
522 | else:
523 | try:
524 | pubKey = RSAPublicKey.from_string(rsa_pub_key)
525 | if HelperMethods.verify_signature(response, pubKey):
526 |
527 | licenseKey = LicenseKey.from_response(response)
528 |
529 | if signature_expiration_interval > 0 and \
530 | (licenseKey.sign_date + datetime.timedelta(days=1*signature_expiration_interval) < datetime.datetime.utcnow()):
531 | return None
532 |
533 | return licenseKey
534 | else:
535 | return None
536 | except Exception:
537 | return None
538 |
539 | @staticmethod
540 | def __load_activated_machines(obj):
541 |
542 | if obj == None:
543 | return None
544 |
545 | arr = []
546 |
547 | for item in obj:
548 | arr.append(ActivatedMachine(**item))
549 |
550 | return arr
551 |
552 | class Response:
553 |
554 | def __init__(self, license_key, signature, result, message):
555 | self.license_key = license_key
556 | self.signature = signature
557 | self.result = result
558 | self.message = message
559 |
560 | @staticmethod
561 | def from_string(responseString):
562 | obj = json.loads(responseString)
563 |
564 | licenseKey = ""
565 | signature = ""
566 | result = 0
567 | message = ""
568 |
569 | if "licenseKey" in obj:
570 | licenseKey = obj["licenseKey"]
571 |
572 | if "signature" in obj:
573 | signature = obj["signature"]
574 |
575 | if "message" in obj:
576 | message = obj["message"]
577 |
578 | if "result" in obj:
579 | result = obj["result"]
580 | else:
581 | result = 1
582 |
583 | return Response(licenseKey, signature, result, message)
584 |
585 | class RSAPublicKey:
586 |
587 | def __init__(self, modulus, exponent):
588 | self.modulus = modulus
589 | self.exponent = exponent
590 |
591 | @staticmethod
592 | def from_string(rsaPubKeyString):
593 | """
594 | The rsaPubKeyString can be found at https://app.cryptolens.io/User/Security.
595 | It should be of the following format:
596 | ...AQAB
597 | """
598 | rsaKey = xml.etree.ElementTree.fromstring(rsaPubKeyString)
599 | return RSAPublicKey(rsaKey.find('Modulus').text, rsaKey.find('Exponent').text)
600 |
--------------------------------------------------------------------------------
/licensing/methods.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Thu Jan 24 08:06:39 2019
4 |
5 | @author: Artem Los
6 | """
7 |
8 | import platform
9 | import uuid
10 | import sys
11 | from licensing.internal import HelperMethods
12 | from licensing.models import *
13 | import json
14 | from urllib.error import URLError, HTTPError
15 |
16 | class Key:
17 |
18 | """
19 | License key related methods. More docs: https://app.cryptolens.io/docs/api/v3/Key.
20 | """
21 |
22 | @staticmethod
23 | def activate(token, rsa_pub_key, product_id, key, machine_code, fields_to_return = 0,\
24 | metadata = False, floating_time_interval = 0,\
25 | max_overdraft = 0, friendly_name = None):
26 |
27 | """
28 | Calls the Activate method in Web API 3 and returns a tuple containing
29 | (LicenseKey, Message). If an error occurs, LicenseKey will be None. If
30 | everything went well, no message will be returned.
31 |
32 | More docs: https://app.cryptolens.io/docs/api/v3/Activate
33 | """
34 |
35 | response = Response("","",0,"")
36 |
37 | try:
38 | response = Response.from_string(HelperMethods.send_request("key/activate", {"token":token,\
39 | "ProductId":product_id,\
40 | "key":key,\
41 | "MachineCode":machine_code,\
42 | "FieldsToReturn":fields_to_return,\
43 | "metadata":metadata,\
44 | "FloatingTimeInterval": floating_time_interval,\
45 | "MaxOverdraft": max_overdraft,\
46 | "FriendlyName" : friendly_name,\
47 | "ModelVersion": 3 ,\
48 | "Sign":"True",\
49 | "SignMethod":1}))
50 | except HTTPError as e:
51 | response = Response.from_string(e.read())
52 | except URLError as e:
53 | return (None, "Could not contact the server. Error message: " + str(e))
54 | except Exception:
55 | return (None, "Could not contact the server.")
56 |
57 | pubkey = RSAPublicKey.from_string(rsa_pub_key)
58 |
59 | if response.result == 1:
60 | return (None, response.message)
61 | else:
62 | try:
63 | if HelperMethods.verify_signature(response, pubkey):
64 | if metadata:
65 |
66 | try:
67 | metadata_s = HelperMethods.verify_signature_metadata(response.metadata["signature"], pubkey)
68 |
69 | if metadata_s[0]:
70 | return (LicenseKey.from_response(response), response.message, json.loads(metadata_s[1]))
71 | else:
72 | return (LicenseKey.from_response(response), response.message, "Signature check for metadata object failed.")
73 | except:
74 | return (LicenseKey.from_response(response), response.message, "Signature check for metadata object failed.")
75 |
76 |
77 | else:
78 | return (LicenseKey.from_response(response), response.message)
79 | else:
80 | return (None, "The signature check failed.")
81 | except Exception as ex:
82 | return (None, "An error occured: {0}".format(ex))
83 |
84 | @staticmethod
85 | def get_key(token, rsa_pub_key, product_id, key, fields_to_return = 0,\
86 | metadata = False, floating_time_interval = 0):
87 |
88 | """
89 | Calls the GetKey method in Web API 3 and returns a tuple containing
90 | (LicenseKey, Message). If an error occurs, LicenseKey will be None. If
91 | everything went well, no message will be returned.
92 |
93 | More docs: https://app.cryptolens.io/docs/api/v3/GetKey
94 | """
95 |
96 | response = Response("","",0,"")
97 |
98 | try:
99 | response = Response.from_string(HelperMethods.send_request("key/getkey", {"token":token,\
100 | "ProductId":product_id,\
101 | "key":key,\
102 | "FieldsToReturn":fields_to_return,\
103 | "metadata":metadata,\
104 | "FloatingTimeInterval": floating_time_interval,\
105 | "Sign":"True",\
106 | "ModelVersion": 3 ,\
107 | "SignMethod":1}))
108 | except HTTPError as e:
109 | response = Response.from_string(e.read())
110 | except URLError as e:
111 | return (None, "Could not contact the server. Error message: " + str(e))
112 | except Exception:
113 | return (None, "Could not contact the server.")
114 |
115 | pubkey = RSAPublicKey.from_string(rsa_pub_key)
116 |
117 | if response.result == 1:
118 | return (None, response.message)
119 | else:
120 | try:
121 | if HelperMethods.verify_signature(response, pubkey):
122 | return (LicenseKey.from_response(response), response.message)
123 | else:
124 | return (None, "The signature check failed.")
125 | except Exception:
126 | return (None, "The signature check failed.")
127 |
128 | @staticmethod
129 | def create_trial_key(token, product_id, machine_code, friendly_name= ""):
130 | """
131 | Calls the CreateTrialKey method in Web API 3 and returns a tuple containing
132 | (LicenseKeyString, Message). If an error occurs, LicenseKeyString will be None. If
133 | everything went well, no message will be returned.
134 |
135 | More docs: https://app.cryptolens.io/docs/api/v3/CreateTrialKey
136 | """
137 |
138 | response = ""
139 |
140 | try:
141 | response = HelperMethods.send_request("key/createtrialkey", {"token":token,\
142 | "ProductId":product_id,\
143 | "MachineCode":machine_code,\
144 | "FriendlyName":friendly_name})
145 | except HTTPError as e:
146 | response = e.read()
147 | except URLError as e:
148 | return (None, "Could not contact the server. Error message: " + str(e))
149 | except Exception:
150 | return (None, "Could not contact the server.")
151 |
152 | jobj = json.loads(response)
153 |
154 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
155 | if jobj != None:
156 | return (None, jobj["message"])
157 | else:
158 | return (None, "Could not contact the server.")
159 |
160 | return (jobj["key"], "")
161 |
162 | @staticmethod
163 | def create_key(token, product_id, period = 0,\
164 | f1=False,\
165 | f2=False,\
166 | f3=False,\
167 | f4=False,\
168 | f5=False,\
169 | f6=False,\
170 | f7=False,\
171 | f8=False,\
172 | notes="",\
173 | block=False,\
174 | customer_id=0,\
175 | new_customer=False,\
176 | add_or_use_existing_customer=False,\
177 | trial_activation=False,\
178 | max_no_of_machines=0,\
179 | no_of_keys=1,\
180 | name = None,\
181 | email = None,\
182 | company_name=None,\
183 | enable_customer_association = False,\
184 | allow_activation_management = False ):
185 | """
186 | This method allows you to create a new license key. The license can
187 | either be standalone or associated to a specific customer. It is also
188 | possible to add a new customer and associate it with the newly created
189 | license using NewCustomer parameter. If you would like to avoid
190 | duplicates based on the email, you can use the AddOrUseExistingCustomer
191 | parameter.
192 |
193 | The parameters "name", "email", "company_name", "enable_customer_association"
194 | and "allow_activation_management" are used to create a new customer (or update an existing one)
195 | and automatically associate it with the newly created license. Please note that you need to use an
196 | access token with both "CreateKey" and "AddCustomer" permissions. Moreover, either
197 | the parameter "new_customer" or "add_or_use_existing_customer" need to be set to True.
198 |
199 | More docs: https://app.cryptolens.io/docs/api/v3/CreateKey/
200 | """
201 |
202 | response = ""
203 |
204 | try:
205 | response = HelperMethods.send_request("key/createkey", {"token":token,\
206 | "ProductId":product_id,\
207 | "Period":period,\
208 | "F1": f1,\
209 | "F2": f2,\
210 | "F3": f3,\
211 | "F4": f4,\
212 | "F5": f5,\
213 | "F6": f6,\
214 | "F7": f7,\
215 | "F8": f8,\
216 | "Notes": notes,\
217 | "Block": block,\
218 | "CustomerId": customer_id,\
219 | "NewCustomer": new_customer,\
220 | "AddOrUseExistingCustomer": add_or_use_existing_customer,\
221 | "TrialActivation": trial_activation,\
222 | "MaxNoOfMachines": max_no_of_machines,\
223 | "NoOfKeys":no_of_keys,\
224 | "Name": name,\
225 | "Email": email,\
226 | "CompanyName": company_name,\
227 | "EnableCustomerAssociation": enable_customer_association,\
228 | "AllowActivationManagement": allow_activation_management})
229 | except HTTPError as e:
230 | response = e.read()
231 | except URLError as e:
232 | return (None, "Could not contact the server. Error message: " + str(e))
233 | except Exception:
234 | return (None, "Could not contact the server.")
235 |
236 | jobj = json.loads(response)
237 |
238 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
239 | if jobj != None:
240 | return (None, jobj["message"])
241 | else:
242 | return (None, "Could not contact the server.")
243 |
244 | return (jobj, "")
245 |
246 |
247 | @staticmethod
248 | def deactivate(token, product_id, key, machine_code, floating = False):
249 | """
250 | Calls the Deactivate method in Web API 3 and returns a tuple containing
251 | (Success, Message). If an error occurs, Success will be False. If
252 | everything went well, Sucess is true and no message will be returned.
253 |
254 | More docs: https://app.cryptolens.io/docs/api/v3/Deactivate
255 | """
256 |
257 | response = ""
258 |
259 | try:
260 | response = HelperMethods.send_request("key/deactivate", {"token":token,\
261 | "ProductId":product_id,\
262 | "Key" : key,\
263 | "Floating" : floating,\
264 | "MachineCode":machine_code})
265 | except HTTPError as e:
266 | response = e.read()
267 | except URLError as e:
268 | return (None, "Could not contact the server. Error message: " + str(e))
269 | except Exception:
270 | return (None, "Could not contact the server.")
271 |
272 | jobj = json.loads(response)
273 |
274 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
275 | if jobj != None:
276 | return (False, jobj["message"])
277 | else:
278 | return (False, "Could not contact the server.")
279 |
280 | return (True, "")
281 |
282 |
283 | @staticmethod
284 | def extend_license(token, product_id, key, no_of_days):
285 | """
286 | This method will extend a license by a certain amount of days.
287 | If the key algorithm in the product is SKGL, the key string will
288 | be changed if necessary. Otherwise, if SKM15 is used, the key will
289 | stay the same. More about the way this method works in Remarks.
290 |
291 | More docs: https://app.cryptolens.io/docs/api/v3/ExtendLicense
292 | """
293 |
294 | response = ""
295 |
296 | try:
297 | response = HelperMethods.send_request("key/ExtendLicense", {"token":token,\
298 | "ProductId":product_id,\
299 | "Key" : key,\
300 | "NoOfDays" : no_of_days})
301 | except HTTPError as e:
302 | response = e.read()
303 | except URLError as e:
304 | return (None, "Could not contact the server. Error message: " + str(e))
305 | except Exception:
306 | return (None, "Could not contact the server.")
307 |
308 | jobj = json.loads(response)
309 |
310 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
311 | if jobj != None:
312 | return (False, jobj["message"])
313 | else:
314 | return (False, "Could not contact the server.")
315 |
316 | return (True, jobj["message"])
317 |
318 | @staticmethod
319 | def change_customer(token, product_id, key, customer_id):
320 | """
321 | This method will change the customer associated with a license.
322 | If the customer is not specified (for example, if CustomerId=0) or
323 | the customer with the provided ID does not exist, any customer that
324 | was previously associated with the license will be dissociated.
325 |
326 | More docs: https://app.cryptolens.io/docs/api/v3/ChangeCustomer
327 | """
328 |
329 | response = ""
330 |
331 | try:
332 | response = HelperMethods.send_request("key/ChangeCustomer", {"token":token,\
333 | "ProductId":product_id,\
334 | "Key" : key,\
335 | "CustomerId" : customer_id})
336 | except HTTPError as e:
337 | response = e.read()
338 | except URLError as e:
339 | return (None, "Could not contact the server. Error message: " + str(e))
340 | except Exception:
341 | return (None, "Could not contact the server.")
342 |
343 | jobj = json.loads(response)
344 |
345 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
346 | if jobj != None:
347 | return (False, jobj["message"])
348 | else:
349 | return (False, "Could not contact the server.")
350 |
351 | return (True, jobj["message"])
352 |
353 | @staticmethod
354 | def unblock_key(token, product_id, key):
355 | """
356 | This method will unblock a specific license key to ensure that it can
357 | be accessed by the Key.Activate method.
358 | To do the reverse, you can use the BlockKey method.
359 |
360 | More docs: https://app.cryptolens.io/docs/api/v3/UnblockKey
361 | """
362 |
363 | response = ""
364 |
365 | try:
366 | response = HelperMethods.send_request("/key/UnblockKey", {"token":token,\
367 | "ProductId":product_id,\
368 | "Key" : key})
369 | except HTTPError as e:
370 | response = e.read()
371 | except URLError as e:
372 | return (None, "Could not contact the server. Error message: " + str(e))
373 | except Exception:
374 | return (None, "Could not contact the server.")
375 |
376 | jobj = json.loads(response)
377 |
378 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
379 | if jobj != None:
380 | return (False, jobj["message"])
381 | else:
382 | return (False, "Could not contact the server.")
383 |
384 | return (True, jobj["message"])
385 |
386 | def block_key(token, product_id, key):
387 | """
388 | This method will block a specific license key to ensure that it will
389 | no longer be possible to activate it. Note, it will still be possible
390 | to access the license key using the GetKey method.
391 | To do the reverse, you can use the Unblock Key method.
392 |
393 | More docs: https://app.cryptolens.io/docs/api/v3/BlockKey
394 | """
395 |
396 | response = ""
397 |
398 | try:
399 | response = HelperMethods.send_request("/key/BlockKey", {"token":token,\
400 | "ProductId":product_id,\
401 | "Key" : key})
402 | except HTTPError as e:
403 | response = e.read()
404 | except URLError as e:
405 | return (None, "Could not contact the server. Error message: " + str(e))
406 | except Exception:
407 | return (None, "Could not contact the server.")
408 |
409 | jobj = json.loads(response)
410 |
411 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
412 | if jobj != None:
413 | return (False, jobj["message"])
414 | else:
415 | return (False, "Could not contact the server.")
416 |
417 | return (True, jobj["message"])
418 |
419 | def machine_lock_limit(token, product_id, key, number_of_machines):
420 | """
421 | This method will change the maximum number of machine codes that
422 | a license key can have.
423 |
424 | More docs: https://app.cryptolens.io/docs/api/v3/MachineLockLimit
425 | """
426 |
427 | response = ""
428 |
429 | try:
430 | response = HelperMethods.send_request("/key/MachineLockLimit", {"token":token,\
431 | "ProductId":product_id,\
432 | "Key" : key,\
433 | "NumberOfMachines": number_of_machines})
434 | except HTTPError as e:
435 | response = e.read()
436 | except URLError as e:
437 | return (None, "Could not contact the server. Error message: " + str(e))
438 | except Exception:
439 | return (None, "Could not contact the server.")
440 |
441 | jobj = json.loads(response)
442 |
443 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
444 | if jobj != None:
445 | return (False, jobj["message"])
446 | else:
447 | return (False, "Could not contact the server.")
448 |
449 | return (True, jobj["message"])
450 |
451 | def change_notes(token, product_id, key, notes):
452 | """
453 | This method will change the content of the notes field of
454 | a given license key.
455 |
456 | More docs: https://app.cryptolens.io/docs/api/v3/ChangeNotes
457 | """
458 |
459 | response = ""
460 |
461 | try:
462 | response = HelperMethods.send_request("/key/ChangeNotes", {"token":token,\
463 | "ProductId":product_id,\
464 | "Key" : key,\
465 | "Notes": notes})
466 | except HTTPError as e:
467 | response = e.read()
468 | except URLError as e:
469 | return (None, "Could not contact the server. Error message: " + str(e))
470 | except Exception:
471 | return (None, "Could not contact the server.")
472 |
473 | jobj = json.loads(response)
474 |
475 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
476 | if jobj != None:
477 | return (False, jobj["message"])
478 | else:
479 | return (False, "Could not contact the server.")
480 |
481 | return (True, jobj["message"])
482 |
483 | def change_reseller(token, product_id, key, reseller_id):
484 | """
485 | This method will change the reseller of a license. If the reseller is
486 | not specified (for example, if ResellerId=0) or the reseller with the
487 | provided ID does not exist, any reseller that was previously associated
488 | with the license will be dissociated.
489 |
490 | More docs: https://app.cryptolens.io/docs/api/v3/ChangeReseller
491 | """
492 |
493 | response = ""
494 |
495 | try:
496 | response = HelperMethods.send_request("/key/ChangeReseller", {"token":token,\
497 | "ProductId":product_id,\
498 | "Key" : key,\
499 | "ResellerId": reseller_id})
500 | except HTTPError as e:
501 | response = e.read()
502 | except URLError as e:
503 | return (None, "Could not contact the server. Error message: " + str(e))
504 | except Exception:
505 | return (None, "Could not contact the server.")
506 |
507 | jobj = json.loads(response)
508 |
509 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
510 | if jobj != None:
511 | return (False, jobj["message"])
512 | else:
513 | return (False, "Could not contact the server.")
514 |
515 | return (True, jobj["message"])
516 |
517 | def create_key_from_template(token, license_template_id):
518 | """
519 | This method will create a license key based on a License Template.
520 | If you want to see all the defined license templates through the API,
521 | this can be accomplished with Get License Templates. An alternative is
522 | to call the Create Key method, which allows you to specify all the
523 | parameters yourself. Note: the "feature lock" field in the access token
524 | can be used to restrict which license tempalte id can be used.
525 |
526 | More docs: https://app.cryptolens.io/docs/api/v3/CreateKeyFromTemplate
527 | """
528 |
529 | response = ""
530 |
531 | try:
532 | response = HelperMethods.send_request("/key/CreateKeyFromTemplate", {"token":token,\
533 | "LicenseTemplateId": license_template_id})
534 | except HTTPError as e:
535 | response = e.read()
536 | except URLError as e:
537 | return (None, "Could not contact the server. Error message: " + str(e))
538 | except Exception:
539 | return (None, "Could not contact the server.")
540 |
541 | jobj = json.loads(response)
542 |
543 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
544 | if jobj != None:
545 | return (False, jobj["message"])
546 | else:
547 | return (False, "Could not contact the server.")
548 |
549 | return (jobj["key"], jobj["rawResponse"], jobj["message"])
550 |
551 | def add_feature(token, product_id, key, feature):
552 | """
553 | This method will set a certain feature (F1..F8) to true.
554 | If the key algorithm in the product is SKGL, the key string will be
555 | changed if necessary. Otherwise, if SKM15 is used, the key will stay
556 | the same. To do the reverse, please see RemoveFeature.
557 |
558 | More docs: https://app.cryptolens.io/docs/api/v3/AddFeature
559 | """
560 |
561 | response = ""
562 |
563 | try:
564 | response = HelperMethods.send_request("/key/AddFeature", {"token":token,\
565 | "ProductId":product_id,\
566 | "Key" : key,\
567 | "Feature" : feature})
568 | except HTTPError as e:
569 | response = e.read()
570 | except URLError as e:
571 | return (None, "Could not contact the server. Error message: " + str(e))
572 | except Exception:
573 | return (None, "Could not contact the server.")
574 |
575 | jobj = json.loads(response)
576 |
577 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
578 | if jobj != None:
579 | return (False, jobj["message"])
580 | else:
581 | return (False, "Could not contact the server.")
582 |
583 | return (True, jobj["message"])
584 |
585 | def remove_feature(token, product_id, key, feature):
586 | """
587 | This method will set a certain feature (F1..F8) to false. If the key
588 | algorithm in the product is SKGL, the key string will be changed if
589 | necessary. Otherwise, if SKM15 is used, the key will stay the same.
590 | To do the reverse, please see AddFeature.
591 |
592 | More docs: https://app.cryptolens.io/docs/api/v3/RemoveFeature
593 | """
594 |
595 | response = ""
596 |
597 | try:
598 | response = HelperMethods.send_request("/key/RemoveFeature", {"token":token,\
599 | "ProductId":product_id,\
600 | "Key" : key,\
601 | "Feature" : feature})
602 | except HTTPError as e:
603 | response = e.read()
604 | except URLError as e:
605 | return (None, "Could not contact the server. Error message: " + str(e))
606 | except Exception:
607 | return (None, "Could not contact the server.")
608 |
609 | jobj = json.loads(response)
610 |
611 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
612 | if jobj != None:
613 | return (False, jobj["message"])
614 | else:
615 | return (False, "Could not contact the server.")
616 |
617 | return (True, jobj["message"])
618 |
619 | class AI:
620 |
621 | @staticmethod
622 | def get_web_api_log(token, product_id = 0, key = "", machine_code="", friendly_name = "",\
623 | limit = 10, starting_after = 0, ending_before=0, order_by=""):
624 |
625 | """
626 | This method will retrieve a list of Web API Logs. All events that get
627 | logged are related to a change of a license key or data object, eg. when
628 | license key gets activated or when a property of data object changes. More details
629 | about the method that was called are specified in the State field.
630 |
631 | More docs: https://app.cryptolens.io/docs/api/v3/GetWebAPILog
632 | """
633 |
634 | response = ""
635 |
636 | try:
637 | response = HelperMethods.send_request("ai/getwebapilog", {"token":token,\
638 | "ProductId":product_id,\
639 | "Key":key,\
640 | "MachineCode":machine_code,\
641 | "FriendlyName":friendly_name,\
642 | "Limit": limit,\
643 | "StartingAfter": starting_after,\
644 | "OrderBy" : order_by,\
645 | "EndingBefore": ending_before})
646 | except HTTPError as e:
647 | response = e.read()
648 | except URLError as e:
649 | return (None, "Could not contact the server. Error message: " + str(e))
650 | except Exception:
651 | return (None, "Could not contact the server.")
652 |
653 | jobj = json.loads(response)
654 |
655 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
656 | if jobj != None:
657 | return (None, jobj["message"])
658 | else:
659 | return (None, "Could not contact the server.")
660 |
661 | return (jobj["logs"], "")
662 |
663 | @staticmethod
664 | def get_events(token, limit = 10, starting_after = 0, product_id = 0,\
665 | key = "", metadata = ""):
666 |
667 | """
668 | This method will retrieve events that were registered using Register event method.
669 |
670 | More docs: https://app.cryptolens.io/api/ai/GetEvents
671 | """
672 |
673 | response = ""
674 |
675 | try:
676 | response = HelperMethods.send_request("ai/GetEvents", {"token":token,\
677 | "ProductId":product_id,\
678 | "Key" : key,\
679 | "Limit": limit,\
680 | "StartingAfter": starting_after})
681 | except HTTPError as e:
682 | response = e.read()
683 | except URLError as e:
684 | return (None, "Could not contact the server. Error message: " + str(e))
685 | except Exception:
686 | return (None, "Could not contact the server.")
687 |
688 | jobj = json.loads(response)
689 |
690 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
691 | if jobj != None:
692 | return (None, jobj["message"])
693 | else:
694 | return (None, "Could not contact the server.")
695 |
696 | return (jobj["events"], "")
697 |
698 | @staticmethod
699 | def register_event(token, product_id=0, key="", machine_code="", feature_name ="",\
700 | event_name="", value=0, currency="", metadata=""):
701 | """
702 | This method will register an event that has occured in either
703 | the client app (eg. start of a certain feature or interaction
704 | within a feature) or in a third party provider (eg. a payment
705 | has occured, etc).
706 |
707 | Note: You can either use this method standalone (eg. by only
708 | providing a machine code/device identifier) or together with
709 | Cryptolens Licensing module (which requires productId and
710 | optionally keyid to be set). The more information that is
711 | provided, the better insights can be provided.
712 |
713 | More docs: https://app.cryptolens.io/api/ai/RegisterEvent
714 | """
715 |
716 | response = ""
717 |
718 | try:
719 | response = HelperMethods.send_request("/ai/RegisterEvent", {"token":token,\
720 | "ProductId":product_id,\
721 | "Key" : key,\
722 | "MachineCode" : machine_code,\
723 | "FeatureName" : feature_name,\
724 | "EventName": event_name,\
725 | "Value" : value,\
726 | "Currency": currency,\
727 | "Metadata" : metadata})
728 | except HTTPError as e:
729 | response = e.read()
730 | except URLError as e:
731 | return (None, "Could not contact the server. Error message: " + str(e))
732 | except Exception:
733 | return (None, "Could not contact the server.")
734 |
735 | jobj = json.loads(response)
736 |
737 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
738 | if jobj != None:
739 | return (False, jobj["message"])
740 | else:
741 | return (False, "Could not contact the server.")
742 |
743 | return (True, jobj["message"])
744 |
745 | class Message:
746 |
747 | @staticmethod
748 | def get_messages(token, channel="", time=0):
749 |
750 | """
751 | This method will return a list of messages that were broadcasted.
752 | You can create new messages here. Messages can be filtered based on the time and the channel.
753 |
754 | More docs: https://app.cryptolens.io/docs/api/v3/GetMessages
755 | """
756 |
757 | try:
758 | response = HelperMethods.send_request("/message/getmessages/", {"token":token, "Channel": channel, "Time": time})
759 | except HTTPError as e:
760 | response = e.read()
761 | except URLError as e:
762 | return (None, "Could not contact the server. Error message: " + str(e))
763 | except Exception:
764 | return (None, "Could not contact the server.")
765 |
766 | jobj = json.loads(response)
767 |
768 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
769 | if jobj != None:
770 | return (None, jobj["message"])
771 | else:
772 | return (None, "Could not contact the server.")
773 |
774 | return (jobj["messages"], "")
775 |
776 |
777 | def create_message(token, content="", channel="", time=0):
778 |
779 | """
780 | This method will create a new message.
781 | This method requires Edit Messages permission.
782 |
783 | More docs: https://app.cryptolens.io/docs/api/v3/CreateMessage
784 | """
785 |
786 | try:
787 | response = HelperMethods.send_request("/message/CreateMessage/", {"token":token, "Channel": channel,"Content":content, "Time": time})
788 | except HTTPError as e:
789 | response = e.read()
790 | except URLError as e:
791 | return (None, "Could not contact the server. Error message: " + str(e))
792 | except Exception:
793 | return (None, "Could not contact the server.")
794 |
795 | jobj = json.loads(response)
796 |
797 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
798 | if jobj != None:
799 | return (None, jobj["message"])
800 | else:
801 | return (None, "Could not contact the server.")
802 |
803 | return (jobj["messageId"], "")
804 |
805 | def remove_message(token, messageId):
806 |
807 | """
808 | This method will remove a message that was previously broadcasted.
809 | This method requires Edit Messages permission.
810 |
811 | More docs: https://app.cryptolens.io/docs/api/v3/RemoveMessage
812 | """
813 |
814 | try:
815 | response = HelperMethods.send_request("/message/RemoveMessage/", {"token":token, "Id": messageId})
816 | except HTTPError as e:
817 | response = e.read()
818 | except URLError as e:
819 | return (None, "Could not contact the server. Error message: " + str(e))
820 | except Exception:
821 | return (None, "Could not contact the server.")
822 |
823 | jobj = json.loads(response)
824 |
825 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
826 | if jobj != None:
827 | return (None, jobj["message"])
828 | else:
829 | return (None, "Could not contact the server.")
830 |
831 | return (True, "")
832 |
833 |
834 | class Product:
835 |
836 | @staticmethod
837 | def get_products(token):
838 |
839 | """
840 | This method will return the list of products. Each product contains fields such as
841 | the name and description, as well feature definitions and data objects. All the fields
842 | of a product are available here: https://app.cryptolens.io/docs/api/v3/model/Product
843 |
844 | More docs: https://app.cryptolens.io/docs/api/v3/GetProducts
845 | """
846 |
847 | try:
848 | response = HelperMethods.send_request("/product/getproducts/", {"token":token})
849 | except HTTPError as e:
850 | response = e.read()
851 | except URLError as e:
852 | return (None, "Could not contact the server. Error message: " + str(e))
853 | except Exception:
854 | return (None, "Could not contact the server.")
855 |
856 | jobj = json.loads(response)
857 |
858 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
859 | if jobj != None:
860 | return (None, jobj["message"])
861 | else:
862 | return (None, "Could not contact the server.")
863 |
864 | return (jobj["products"], "")
865 |
866 | @staticmethod
867 | def get_keys(token, product_id, page = 1, order_by="ID ascending", search_query=""):
868 |
869 | """
870 | This method will return a list of keys for a given product.
871 | Please keep in mind that although each license key will be
872 | of the License Key type, the fields related to signing operations
873 | will be left empty. Instead, if you want to get a signed license key
874 | (for example, to achieve offline key activation), please use the
875 | Activation method instead.
876 |
877 | More docs: https://app.cryptolens.io/docs/api/v3/GetKeys
878 | """
879 |
880 | try:
881 | response = HelperMethods.send_request("/product/getkeys/",\
882 | {"token":token,\
883 | "ProductId" : product_id,\
884 | "Page" : page,\
885 | "OrderBy" : order_by,\
886 | "SearchQuery" : search_query\
887 | })
888 | except HTTPError as e:
889 | response = e.read()
890 | except URLError as e:
891 | return (None, "Could not contact the server. Error message: " + str(e))
892 | except Exception:
893 | return (None, "Could not contact the server.")
894 |
895 | jobj = json.loads(response)
896 |
897 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
898 | if jobj != None:
899 | return (None, jobj["message"])
900 | else:
901 | return (None, "Could not contact the server.")
902 |
903 | return (jobj["licenseKeys"], "", {"returned":jobj["returned"], "total":jobj["total"], "pageCount":jobj["pageCount"]})
904 |
905 |
906 | class Customer:
907 |
908 | @staticmethod
909 | def add_customer(token, name = "", email = "", company_name="",\
910 | enable_customer_association = False,\
911 | allow_activation_management = False ):
912 |
913 | """
914 | This method will add new customer.
915 |
916 | More docs: https://app.cryptolens.io/docs/api/v3/AddCustomer
917 | """
918 |
919 | try:
920 | response = HelperMethods.send_request("/customer/addcustomer/",\
921 | {"token":token,\
922 | "Name": name,\
923 | "Email": email,\
924 | "CompanyName": company_name,\
925 | "EnableCustomerAssociation": enable_customer_association,\
926 | "AllowActivationManagement": allow_activation_management
927 | })
928 | except HTTPError as e:
929 | response = e.read()
930 | except URLError as e:
931 | return (None, "Could not contact the server. Error message: " + str(e))
932 | except Exception:
933 | return (None, "Could not contact the server.")
934 |
935 | jobj = json.loads(response)
936 |
937 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
938 | if jobj != None:
939 | return (None, jobj["message"])
940 | else:
941 | return (None, "Could not contact the server.")
942 |
943 | return (jobj, "")
944 |
945 | @staticmethod
946 | def get_customer_licenses(token, customer_id, detailed=False, metadata=False):
947 |
948 | """
949 | This method will return a list of license keys that belong to a certain customer.
950 |
951 | More docs: https://app.cryptolens.io/docs/api/v3/GetCustomerLicenses
952 | """
953 |
954 | try:
955 | response = HelperMethods.send_request("/customer/GetCustomerLicenses/",\
956 | {"token":token,\
957 | "customerId" : customer_id,\
958 | "detailed" : detailed,\
959 | "metadata" : metadata
960 | })
961 | except HTTPError as e:
962 | response = e.read()
963 | except URLError as e:
964 | return (None, "Could not contact the server. Error message: " + str(e))
965 | except Exception:
966 | return (None, "Could not contact the server.")
967 |
968 | jobj = json.loads(response)
969 |
970 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
971 | if jobj != None:
972 | return (None, jobj["message"])
973 | else:
974 | return (None, "Could not contact the server.")
975 |
976 | return (jobj, "")
977 |
978 | @staticmethod
979 | def get_customer_licenses_by_secret(token, secret, detailed=False, metadata=False):
980 |
981 | """
982 | This method will return a list of license keys that belong to a certain customer.
983 |
984 | More docs: https://app.cryptolens.io/docs/api/v3/GetCustomerLicenses
985 | """
986 |
987 | try:
988 | response = HelperMethods.send_request("/customer/GetCustomerLicensesBySecret/",\
989 | {"token":token,\
990 | "secret" : secret,\
991 | "detailed" : detailed,\
992 | "metadata" : metadata
993 | })
994 | except HTTPError as e:
995 | response = e.read()
996 | except URLError as e:
997 | return (None, "Could not contact the server. Error message: " + str(e))
998 | except Exception:
999 | return (None, "Could not contact the server.")
1000 |
1001 | jobj = json.loads(response)
1002 |
1003 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1004 | if jobj != None:
1005 | return (None, jobj["message"])
1006 | else:
1007 | return (None, "Could not contact the server.")
1008 |
1009 | return (jobj, "")
1010 |
1011 |
1012 | class Data:
1013 |
1014 | """
1015 | Data object related methods
1016 | """
1017 |
1018 | @staticmethod
1019 | def increment_int_value_to_key(token, product_id, key, object_id=0, name = "",\
1020 | int_value=0, enable_bound=False, bound=0):
1021 |
1022 | """
1023 | This method will increment the int value of a data object associated with a license key.
1024 |
1025 | When creating an access token to this method, remember to include "IncrementIntValue" permission and
1026 | set the "Lock to key" value to -1.
1027 |
1028 | Note: either an object_id or name (provided there are no duplicates) is required.
1029 |
1030 | More docs: https://app.cryptolens.io/docs/api/v3/IncrementIntValue (see parameters under Method 2)
1031 | """
1032 |
1033 | try:
1034 | response = HelperMethods.send_request("/data/IncrementIntValueToKey/",\
1035 | {"token":token,\
1036 | "ProductId" : product_id,\
1037 | "Key" : key,\
1038 | "Id" : object_id,\
1039 | "Name" : name,\
1040 | "IntValue": int_value ,\
1041 | "EnableBound": str(enable_bound),\
1042 | "Bound" : bound
1043 | })
1044 | except HTTPError as e:
1045 | response = e.read()
1046 | except URLError as e:
1047 | return (None, "Could not contact the server. Error message: " + str(e))
1048 | except Exception:
1049 | return (None, "Could not contact the server.")
1050 |
1051 | jobj = json.loads(response)
1052 |
1053 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1054 | if jobj != None:
1055 | return (None, jobj["message"])
1056 | else:
1057 | return (None, "Could not contact the server.")
1058 |
1059 | return (jobj, "")
1060 |
1061 | @staticmethod
1062 | def decrement_int_value_to_key(token, product_id, key, object_id=0, name="",\
1063 | int_value=0, enable_bound=False, bound=0):
1064 |
1065 | """
1066 | This method will decrement the int value of a data object associated with a license key.
1067 |
1068 | When creating an access token to this method, remember to include "DecrementIntValue" permission and
1069 | set the "Lock to key" value to -1.
1070 |
1071 | Note: either an object_id or name (provided there are no duplicates) is required.
1072 |
1073 | More docs: https://app.cryptolens.io/docs/api/v3/DecrementIntValue (see parameters under Method 2)
1074 | """
1075 |
1076 | try:
1077 | response = HelperMethods.send_request("/data/DecrementIntValueToKey/",\
1078 | {"token":token,\
1079 | "ProductId" : product_id,\
1080 | "Key" : key,\
1081 | "Id" : object_id,\
1082 | "Name" : name,\
1083 | "IntValue": int_value ,\
1084 | "EnableBound": str(enable_bound),\
1085 | "Bound" : bound
1086 | })
1087 | except HTTPError as e:
1088 | response = e.read()
1089 | except URLError as e:
1090 | return (None, "Could not contact the server. Error message: " + str(e))
1091 | except Exception:
1092 | return (None, "Could not contact the server.")
1093 |
1094 | jobj = json.loads(response)
1095 |
1096 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1097 | if jobj != None:
1098 | return (None, jobj["message"])
1099 | else:
1100 | return (None, "Could not contact the server.")
1101 |
1102 | return (jobj, "")
1103 |
1104 | @staticmethod
1105 | def add_data_object_to_key(token, product_id, key, name = "", string_value="",\
1106 | int_value=0, check_for_duplicates=False):
1107 |
1108 | """
1109 | This method will add a new Data Object to a license key.
1110 |
1111 | More docs: https://app.cryptolens.io/docs/api/v3/AddDataObject (see parameters under Method 2)
1112 | """
1113 |
1114 | try:
1115 | response = HelperMethods.send_request("/data/AddDataObjectToKey/",\
1116 | {"token":token,\
1117 | "ProductId" : product_id,\
1118 | "Key" : key,\
1119 | "Name" : name,\
1120 | "IntValue": int_value ,\
1121 | "StringValue": string_value ,\
1122 | "CheckForDuplicates" : str(check_for_duplicates)
1123 | })
1124 | except HTTPError as e:
1125 | response = e.read()
1126 | except URLError as e:
1127 | return (None, "Could not contact the server. Error message: " + str(e))
1128 | except Exception:
1129 | return (None, "Could not contact the server.")
1130 |
1131 | jobj = json.loads(response)
1132 |
1133 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1134 | if jobj != None:
1135 | return (None, jobj["message"])
1136 | else:
1137 | return (None, "Could not contact the server.")
1138 |
1139 | return (jobj, "")
1140 |
1141 | @staticmethod
1142 | def remove_data_object_to_key(token, product_id, key, object_id=0, name = ""):
1143 |
1144 | """
1145 | This method will add a new Data Object to a license key.
1146 |
1147 | Note: either an object_id or name (provided there are no duplicates) is required.
1148 |
1149 | More docs: https://app.cryptolens.io/docs/api/v3/RemoveDataObject (see parameters under Method 2)
1150 | """
1151 |
1152 | try:
1153 | response = HelperMethods.send_request("/data/RemoveDataObjectToKey/",\
1154 | {"token":token,\
1155 | "ProductId" : product_id,\
1156 | "Key" : key,\
1157 | "Name" : name,\
1158 | "Id": object_id })
1159 | except HTTPError as e:
1160 | response = e.read()
1161 | except URLError as e:
1162 | return (None, "Could not contact the server. Error message: " + str(e))
1163 | except Exception:
1164 | return (None, "Could not contact the server.")
1165 |
1166 | jobj = json.loads(response)
1167 |
1168 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1169 | if jobj != None:
1170 | return (None, jobj["message"])
1171 | else:
1172 | return (None, "Could not contact the server.")
1173 |
1174 | return (jobj, "")
1175 |
1176 | @staticmethod
1177 | def add_data_object_to_machine(token, product_id, key, machine_code, name = "", string_value="",\
1178 | int_value=0, check_for_duplicates=False):
1179 |
1180 | """
1181 | This method will add a new Data Object to Machine.
1182 |
1183 | More docs: https://app.cryptolens.io/docs/api/v3/AddDataObject (see parameters under Method 3)
1184 | """
1185 |
1186 | try:
1187 | response = HelperMethods.send_request("/data/AddDataObjectToMachineCode/",\
1188 | {"token":token,\
1189 | "ProductId" : product_id,\
1190 | "Key" : key,\
1191 | "Name" : name,\
1192 | "IntValue": int_value ,\
1193 | "StringValue": string_value ,\
1194 | "CheckForDuplicates" : str(check_for_duplicates), \
1195 | "MachineCode": machine_code
1196 | })
1197 | except HTTPError as e:
1198 | response = e.read()
1199 | except URLError as e:
1200 | return (None, "Could not contact the server. Error message: " + str(e))
1201 | except Exception:
1202 | return (None, "Could not contact the server.")
1203 |
1204 | jobj = json.loads(response)
1205 |
1206 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1207 | if jobj != None:
1208 | return (None, jobj["message"])
1209 | else:
1210 | return (None, "Could not contact the server.")
1211 |
1212 | return (jobj, "")
1213 |
1214 | @staticmethod
1215 | def remove_data_object_to_machine(token, product_id, key, machine_code, object_id=0, name = ""):
1216 |
1217 | """
1218 | This method will remove existing Data Object from Machine Code.
1219 |
1220 | More docs: https://app.cryptolens.io/docs/api/v3/RemoveDataObject (see parameters under Method 3)
1221 | """
1222 |
1223 | try:
1224 | response = HelperMethods.send_request("/data/RemoveDataObjectToMachineCode/",\
1225 | {"token":token,\
1226 | "ProductId" : product_id,\
1227 | "Key" : key,\
1228 | "MachineCode": machine_code,\
1229 | "Name" : name,\
1230 | "Id": object_id })
1231 | except HTTPError as e:
1232 | response = e.read()
1233 | except URLError as e:
1234 | return (None, "Could not contact the server. Error message: " + str(e))
1235 | except Exception:
1236 | return (None, "Could not contact the server.")
1237 |
1238 | jobj = json.loads(response)
1239 |
1240 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1241 | if jobj != None:
1242 | return (None, jobj["message"])
1243 | else:
1244 | return (None, "Could not contact the server.")
1245 |
1246 | return (jobj, "")
1247 |
1248 | @staticmethod
1249 | def list_machine_data_objects(token, product_id, key, machine_code, \
1250 | name_contains=""):
1251 |
1252 | """
1253 | This method will list Data Objects for Machine.
1254 |
1255 | More docs: https://app.cryptolens.io/docs/api/v3/ListDataObjects (see parameters under Method 3)
1256 | """
1257 |
1258 | try:
1259 | response = HelperMethods.send_request("/data/ListDataObjectsToMachineCode/",\
1260 | {"token":token,\
1261 | "ProductId" : product_id,\
1262 | "Key" : key,\
1263 | "MachineCode": machine_code,\
1264 | "Contains": name_contains
1265 | })
1266 | except HTTPError as e:
1267 | response = e.read()
1268 | except URLError as e:
1269 | return (None, "Could not contact the server. Error message: " + str(e))
1270 | except Exception:
1271 | return (None, "Could not contact the server.")
1272 |
1273 | jobj = json.loads(response)
1274 |
1275 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1276 | if jobj != None:
1277 | return (None, jobj["message"])
1278 | else:
1279 | return (None, "Could not contact the server.")
1280 |
1281 | return (jobj, "")
1282 |
1283 | @staticmethod
1284 | def list_key_data_objects(token, product_id, key, \
1285 | name_contains=""):
1286 |
1287 | """
1288 | This method will list Data Objects for License Key.
1289 |
1290 | More docs: https://app.cryptolens.io/docs/api/v3/ListDataObjects (see parameters under Method 2)
1291 | """
1292 |
1293 | try:
1294 | response = HelperMethods.send_request("/data/ListDataObjectsToKey/",\
1295 | {"token":token,\
1296 | "ProductId" : product_id,\
1297 | "Key" : key,\
1298 | "Contains": name_contains
1299 | })
1300 | except HTTPError as e:
1301 | response = e.read()
1302 | except URLError as e:
1303 | return (None, "Could not contact the server. Error message: " + str(e))
1304 | except Exception:
1305 | return (None, "Could not contact the server.")
1306 |
1307 | jobj = json.loads(response)
1308 |
1309 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1310 | if jobj != None:
1311 | return (None, jobj["message"])
1312 | else:
1313 | return (None, "Could not contact the server.")
1314 |
1315 | return (jobj, "")
1316 |
1317 |
1318 | class PaymentForm:
1319 |
1320 | @staticmethod
1321 | def create_session(token, payment_form_id, currency, expires, price=None,\
1322 | heading = None, product_name = None, custom_field='',\
1323 | metadata = None):
1324 |
1325 |
1326 | """
1327 | This method will create a new session for a Payment Form.
1328 | It allows you to customize appearance of the form (such as price, heading, etc).
1329 | You should only create new sessions from a server side (i.e. never directly from your application).
1330 | Note, session will only work once and it will eventually expire depending on Expires parameter.
1331 |
1332 | More docs: https://app.cryptolens.io/docs/api/v3/PFCreateSession
1333 | """
1334 |
1335 | try:
1336 | response = HelperMethods.send_request("/paymentform/CreateSession/",\
1337 | {"token":token,\
1338 | "PaymentFormId" : payment_form_id,\
1339 | "Price" : price,\
1340 | "Currency" : currency,\
1341 | "Heading": heading ,\
1342 | "ProductName": product_name,\
1343 | "CustomField" : custom_field,\
1344 | "Metadata" : metadata,\
1345 | "Expires" : expires,\
1346 | })
1347 | except HTTPError as e:
1348 | response = e.read()
1349 | except URLError as e:
1350 | return (None, "Could not contact the server. Error message: " + str(e))
1351 | except Exception:
1352 | return (None, "Could not contact the server.")
1353 |
1354 | jobj = json.loads(response)
1355 |
1356 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1357 | if jobj != None:
1358 | return (None, jobj["message"])
1359 | else:
1360 | return (None, "Could not contact the server.")
1361 |
1362 | return (jobj, "")
1363 |
1364 | class Helpers:
1365 |
1366 |
1367 | def __read_registry_value(key, subkey, value_name):
1368 |
1369 | import winreg
1370 |
1371 | """
1372 | Reads a value from the Windows Registry.
1373 |
1374 | Parameters:
1375 | key (int): The registry root key (e.g., winreg.HKEY_LOCAL_MACHINE).
1376 | subkey (str): The path to the subkey.
1377 | value_name (str): The name of the value to read.
1378 |
1379 | Returns:
1380 | str: The value read from the registry, or an error message if not found.
1381 | """
1382 | try:
1383 | # Open the registry key
1384 | registry_key = winreg.OpenKey(key, subkey, 0, winreg.KEY_READ)
1385 |
1386 | # Query the value
1387 | value, reg_type = winreg.QueryValueEx(registry_key, value_name)
1388 |
1389 | # Close the registry key
1390 | winreg.CloseKey(registry_key)
1391 |
1392 | return value
1393 |
1394 | except FileNotFoundError:
1395 | return None
1396 | except Exception as e:
1397 | return None #str(e)
1398 |
1399 |
1400 | @staticmethod
1401 | def GetMachineCode(v=1):
1402 |
1403 | """
1404 | Get a unique identifier for this device. If you want the machine code to be the same in .NET on Windows, you
1405 | can set v=2. More information is available here: https://help.cryptolens.io/faq/index#machine-code-generation
1406 |
1407 | Note: if we are unable to compute the machine code, None will be returned. Please make sure
1408 | to check this in production code.
1409 | """
1410 |
1411 | if "windows" in platform.platform().lower():
1412 |
1413 | import winreg
1414 |
1415 | seed = ""
1416 |
1417 | if v==2:
1418 | seed = HelperMethods.start_process_ps_v2()
1419 | else:
1420 | seed = HelperMethods.start_process(["cmd.exe", "/C", "wmic","csproduct", "get", "uuid"],v)
1421 |
1422 | if seed == "" or seed == None:
1423 | machineGUID = Helpers.__read_registry_value(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Cryptography", "MachineGuid")
1424 |
1425 | if machineGUID != None and machineGUID != "":
1426 | return HelperMethods.get_SHA256(machineGUID)
1427 | return None
1428 | else:
1429 | return HelperMethods.get_SHA256(seed)
1430 |
1431 |
1432 | elif "mac" in platform.platform().lower() or "darwin" in platform.platform().lower():
1433 | res = HelperMethods.start_process(["system_profiler","SPHardwareDataType"])
1434 | seed = res[res.index("UUID"):].strip()
1435 |
1436 | if seed == "":
1437 | return None
1438 | else:
1439 | return HelperMethods.get_SHA256(seed)
1440 |
1441 | elif "linux" in platform.platform().lower() :
1442 | seed = HelperMethods.compute_machine_code()
1443 | if seed == "":
1444 | return None
1445 | else:
1446 | return HelperMethods.get_SHA256(seed)
1447 | else:
1448 | seed = HelperMethods.compute_machine_code()
1449 | if seed == "":
1450 | return None
1451 | else:
1452 | return HelperMethods.get_SHA256(seed)
1453 |
1454 | @staticmethod
1455 | def IsOnRightMachine(license_key, is_floating_license = False, allow_overdraft=False, v = 1, custom_machine_code = None):
1456 |
1457 | """
1458 | Check if the device is registered with the license key.
1459 | The version parameter is related to the one in GetMachineCode method.
1460 | """
1461 |
1462 | current_mid = ""
1463 |
1464 | if custom_machine_code == None:
1465 | current_mid = Helpers.GetMachineCode(v)
1466 | else:
1467 | current_mid = custom_machine_code
1468 |
1469 | if license_key.activated_machines == None:
1470 | return False
1471 |
1472 | if is_floating_license:
1473 | for act_machine in license_key.activated_machines:
1474 | if act_machine.Mid[9:] == current_mid or\
1475 | allow_overdraft and act_machine.Mid[19:] == current_mid:
1476 | return True
1477 | else:
1478 | for act_machine in license_key.activated_machines:
1479 | if current_mid == act_machine.Mid:
1480 | return True
1481 |
1482 | return False
1483 |
1484 |
1485 | @staticmethod
1486 | def GetMACAddress():
1487 |
1488 | """
1489 | An alternative way to compute the machine code (device identifier).
1490 | This method is especially useful if you plan to target multiple platforms.
1491 | """
1492 |
1493 | import uuid
1494 |
1495 | return ':'.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff) for ele in range(0,8*6,8)][::-1])
1496 |
1497 | def HasNotExpired(license_key, allow_usage_on_expiry_date=True):
1498 |
1499 | """
1500 | Checks that the license key has not expired. Cryptolens offers automatic blocking of licenses
1501 | on the server side, and it is recommended to set it up instead of relying on the client side (unless
1502 | your application is running in offline mode). For more details, please review
1503 | https://help.cryptolens.io/web-interface/keys-that-dont-expire
1504 |
1505 | Parameters:
1506 | @license_key The license key object.
1507 | @allow_usage_on_expiry_date If set to true, the license will be considered valid on the day it expires.
1508 | """
1509 |
1510 | import datetime
1511 |
1512 | if license_key == None:
1513 | return False
1514 |
1515 | diff = license_key.expires.replace(tzinfo=datetime.timezone.utc) - datetime.datetime.now(datetime.timezone.utc)
1516 |
1517 | if allow_usage_on_expiry_date and diff >= datetime.timedelta(0) or not(allow_usage_on_expiry_date) and diff > 0:
1518 | return True
1519 |
1520 | return False
1521 |
1522 | def HasFeature(license_key, feature_name):
1523 |
1524 | """
1525 | Uses a special data object associated with the license key to determine if a certain feature exists (instead of the 8 feature flags).
1526 | Formatting: The name of the data object should be 'cryptolens_features' and it should be structured as a JSON array.
1527 |
1528 | For example, ["f1", "f2"]
means f1 and f2 are true. You can also have feature bundling, eg.
["f1", ["f2",["voice","image"]]]
1529 | which means that f1 and f2 are true, as well as f2.voice and f2.image. You can set any depth, eg. you can have
1530 | ["f1", ["f2",[["voice",["all"]], "image"]]]
means f2.voice.all is true as well as f2.voice and f2.
1531 | The dots symbol is used to specify the "sub-features".
1532 |
1533 | Read more here: https://help.cryptolens.io/web-interface/feature-templates
1534 |
1535 | Parameters:
1536 | @license_key The license key object.
1537 | @feature_name For example, "f2.voice.all".
1538 |
1539 | """
1540 |
1541 | if license_key.data_objects == None:
1542 | return False
1543 |
1544 | features = None
1545 |
1546 | for dobj in license_key.data_objects:
1547 |
1548 | if dobj["Name"] == 'cryptolens_features':
1549 | features = dobj["StringValue"]
1550 | break
1551 |
1552 | if features == None or features.strip() == "":
1553 | return False
1554 |
1555 | array = json.loads(features)
1556 |
1557 | feature_path = feature_name.split(".")
1558 |
1559 | found = False
1560 |
1561 | for i in range(len(feature_path)):
1562 |
1563 | found = False
1564 | index = -1
1565 |
1566 | for j in range(len(array)):
1567 |
1568 | if not(isinstance(array[j], list)) and array[j] == feature_path[i]:
1569 | found = True
1570 | break
1571 | elif isinstance(array[j], list) and array[j][0] == feature_path[i]:
1572 | found = True
1573 | index = j
1574 |
1575 | if not(found):
1576 | return False
1577 |
1578 | if i+1 < len(feature_path) and index != -1:
1579 | array = array[index][1]
1580 |
1581 | if not(found):
1582 | return False
1583 |
1584 | return True
1585 |
1586 | class Subscription:
1587 | """
1588 | Subscription related methods
1589 | """
1590 |
1591 | @staticmethod
1592 | def record_usage_to_stripe(token, product_id, key, amount=""):
1593 |
1594 | """
1595 | This method records uses Stripe's metered billing to record usage for a certain subscription. In order to use this method,
1596 | you need to have set up recurring billing. A record will be created using Stripe's API with action set to 'increment'
1597 |
1598 | More docs: https://app.cryptolens.io/docs/api/v3/RecordUsage
1599 | """
1600 |
1601 | try:
1602 | response = HelperMethods.send_request("/subscription/RecordUsage/",\
1603 | {"token":token,\
1604 | "ProductId" : product_id,\
1605 | "Key" : key,\
1606 | "Amount" : amount,
1607 | })
1608 | except HTTPError as e:
1609 | response = e.read()
1610 | except URLError as e:
1611 | return (None, "Could not contact the server. Error message: " + str(e))
1612 | except Exception:
1613 | return (None, "Could not contact the server.")
1614 |
1615 | jobj = json.loads(response)
1616 |
1617 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1618 | if jobj != None:
1619 | return (None, jobj["message"])
1620 | else:
1621 | return (None, "Could not contact the server.")
1622 |
1623 | return (jobj, "")
1624 |
1625 | class User:
1626 |
1627 | """
1628 | The idea behind user authentication is to allow you to authenticate
1629 | users using their crendntials (i.e. username and password) to verify their
1630 | license. You can use their username and password to retrieve their
1631 | licenses instead of asking for a license key.
1632 |
1633 | This is similar to obtaining all licenses assigned to a customer
1634 | using customer secret, with the difference that the user can pick both
1635 | the username and password, as well as restore a forgotten password.
1636 |
1637 | For more information, please see
1638 | https://help.cryptolens.io/examples/user-verification and
1639 | https://app.cryptolens.io/docs/api/v3/UserAuth
1640 | """
1641 |
1642 | @staticmethod
1643 | def login(token, username, password):
1644 |
1645 | """
1646 | This method will return all licenses that belong to the user.
1647 | This method can be called with an access token that has UserAuthNormal
1648 | and UserAuthAdmin permission.
1649 |
1650 | More docs: https://app.cryptolens.io/docs/api/v3/Login
1651 | """
1652 |
1653 | try:
1654 | response = HelperMethods.send_request("/userauth/login/", {"token":token, "username":username, "password":password})
1655 | except HTTPError as e:
1656 | response = e.read()
1657 | except URLError as e:
1658 | return (None, "Could not contact the server. Error message: " + str(e))
1659 | except Exception:
1660 | return (None, "Could not contact the server.")
1661 |
1662 | jobj = json.loads(response)
1663 |
1664 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1665 | if jobj != None:
1666 | return (None, jobj["message"])
1667 | else:
1668 | return (None, "Could not contact the server.")
1669 |
1670 | return (jobj["licenseKeys"], "")
1671 |
1672 | """
1673 | The idea behind user authentication is to allow you to authenticate
1674 | users using their crendntials (i.e. username and password) to verify their
1675 | license. You can use their username and password to retrieve their
1676 | licenses instead of asking for a license key.
1677 |
1678 | This is similar to obtaining all licenses assigned to a customer
1679 | using customer secret, with the difference that the user can pick both
1680 | the username and password, as well as restore a forgotten password.
1681 |
1682 | For more information, please see
1683 | https://help.cryptolens.io/examples/user-verification and
1684 | https://app.cryptolens.io/docs/api/v3/UserAuth
1685 | """
1686 |
1687 | @staticmethod
1688 | def register(token, username, password, email = "", customerId = 0):
1689 |
1690 | """
1691 | This method will register a new user. Please note that calling this
1692 | method requires a UserAuthAdmin token.
1693 |
1694 | More docs: https://app.cryptolens.io/docs/api/v3/Register
1695 | """
1696 |
1697 | try:
1698 | response = HelperMethods.send_request("/userauth/Register/",\
1699 | {"token":token,\
1700 | "username":username,\
1701 | "password":password,\
1702 | "email":email,\
1703 | "customerid":customerId})
1704 | except HTTPError as e:
1705 | response = e.read()
1706 | except URLError as e:
1707 | return (None, "Could not contact the server. Error message: " + str(e))
1708 | except Exception:
1709 | return (None, "Could not contact the server.")
1710 |
1711 | jobj = json.loads(response)
1712 |
1713 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1714 | if jobj != None:
1715 | return (None, jobj["message"])
1716 | else:
1717 | return (None, "Could not contact the server.")
1718 |
1719 | return (jobj, "")
1720 |
1721 | @staticmethod
1722 | def associate(token, username, customer_id=0):
1723 |
1724 | """
1725 | Associates a user with a customer object. Please note that calling
1726 | this method requires a UserAuthAdmin token.
1727 |
1728 | More docs: https://app.cryptolens.io/docs/api/v3/Associate
1729 | """
1730 |
1731 | try:
1732 | response = HelperMethods.send_request("/userauth/Associate/",\
1733 | {"token":token,\
1734 | "username":username,\
1735 | "customerid":customer_id})
1736 | except HTTPError as e:
1737 | response = e.read()
1738 | except URLError as e:
1739 | return (None, "Could not contact the server. Error message: " + str(e))
1740 | except Exception:
1741 | return (None, "Could not contact the server.")
1742 |
1743 | jobj = json.loads(response)
1744 |
1745 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1746 | if jobj != None:
1747 | return (None, jobj["message"])
1748 | else:
1749 | return (None, "Could not contact the server.")
1750 |
1751 | return (jobj, "")
1752 |
1753 | @staticmethod
1754 | def dissociate(token, username):
1755 |
1756 | """
1757 | Dissociates a user from a customer customer object. Please note that
1758 | calling this method requires a UserAuthAdmin token.
1759 |
1760 | More docs: https://app.cryptolens.io/docs/api/v3/Dissociate
1761 | """
1762 |
1763 | try:
1764 | response = HelperMethods.send_request("/userauth/Dissociate/",\
1765 | {"token":token,\
1766 | "username":username})
1767 | except HTTPError as e:
1768 | response = e.read()
1769 | except URLError as e:
1770 | return (None, "Could not contact the server. Error message: " + str(e))
1771 | except Exception:
1772 | return (None, "Could not contact the server.")
1773 |
1774 | jobj = json.loads(response)
1775 |
1776 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1777 | if jobj != None:
1778 | return (None, jobj["message"])
1779 | else:
1780 | return (None, "Could not contact the server.")
1781 |
1782 | return (jobj, "")
1783 |
1784 | @staticmethod
1785 | def get_users(token, customer_id = 0):
1786 |
1787 | """
1788 | List all registered users. Please note that calling this method
1789 | requires a UserAuthAdmin token.
1790 |
1791 | More docs: https://app.cryptolens.io/docs/api/v3/GetUsers
1792 | """
1793 |
1794 | try:
1795 | response = HelperMethods.send_request("/userauth/GetUsers/",\
1796 | {"token":token,\
1797 | "customerid":customer_id})
1798 | except HTTPError as e:
1799 | response = e.read()
1800 | except URLError as e:
1801 | return (None, "Could not contact the server. Error message: " + str(e))
1802 | except Exception:
1803 | return (None, "Could not contact the server.")
1804 |
1805 | jobj = json.loads(response)
1806 |
1807 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1808 | if jobj != None:
1809 | return (None, jobj["message"])
1810 | else:
1811 | return (None, "Could not contact the server.")
1812 |
1813 | return (jobj["users"], "")
1814 |
1815 | @staticmethod
1816 | def change_password(token, username, new_password, old_password="", password_reset_token="", admin_mode=False):
1817 |
1818 | """
1819 | This method will change the password of a user. It supports 3 modes of
1820 | operation. With an access token that has UserAuthNormal permission
1821 | (i.e. without admin permission), the password can either be changed by
1822 | providing the old password or a password reset token, which can be
1823 | generated using Reset Password Token method. Finally, if you call this
1824 | method with an access token that has UserAuthAdmin permission, it will
1825 | allow you to set AdminMode to True and only provide the NewPassword.
1826 |
1827 | More docs: https://app.cryptolens.io/docs/api/v3/ChangePassword
1828 | """
1829 |
1830 | try:
1831 | response = HelperMethods.send_request("/userauth/ChangePassword/",\
1832 | {"token":token,\
1833 | "username":username,\
1834 | "OldPassword": old_password,\
1835 | "NewPassword":new_password,\
1836 | "PasswordResetToken": password_reset_token,\
1837 | "AdminMode":admin_mode})
1838 | except HTTPError as e:
1839 | response = e.read()
1840 | except URLError as e:
1841 | return (None, "Could not contact the server. Error message: " + str(e))
1842 | except Exception:
1843 | return (None, "Could not contact the server.")
1844 |
1845 | jobj = json.loads(response)
1846 |
1847 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1848 | if jobj != None:
1849 | return (None, jobj["message"])
1850 | else:
1851 | return (None, "Could not contact the server.")
1852 |
1853 | return (jobj, "")
1854 |
1855 |
1856 | @staticmethod
1857 | def reset_password_token(token, username):
1858 |
1859 | """
1860 | This method allows you to retrive the password reset token that you
1861 | can use when calling Change Password method. Please note that calling
1862 | this method requires a UserAuthAdmin token.
1863 |
1864 | More docs: https://app.cryptolens.io/docs/api/v3/ResetPasswordToken
1865 | """
1866 |
1867 | try:
1868 | response = HelperMethods.send_request("/userauth/ResetPasswordToken/",\
1869 | {"token":token,\
1870 | "username":username})
1871 | except HTTPError as e:
1872 | response = e.read()
1873 | except URLError as e:
1874 | return (None, "Could not contact the server. Error message: " + str(e))
1875 | except Exception:
1876 | return (None, "Could not contact the server.")
1877 |
1878 | jobj = json.loads(response)
1879 |
1880 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1881 | if jobj != None:
1882 | return (None, jobj["message"])
1883 | else:
1884 | return (None, "Could not contact the server.")
1885 |
1886 | return (jobj["passwordResetToken"], "")
1887 |
1888 |
1889 | @staticmethod
1890 | def remove_user(token, username):
1891 |
1892 | """
1893 | This method removes a user. Please note that calling this method
1894 | requires a UserAuthAdmin token.
1895 |
1896 | More docs: https://app.cryptolens.io/docs/api/v3/RemoveUser
1897 | """
1898 |
1899 | try:
1900 | response = HelperMethods.send_request("/userauth/RemoveUser/",\
1901 | {"token":token,\
1902 | "username":username})
1903 | except HTTPError as e:
1904 | response = e.read()
1905 | except URLError as e:
1906 | return (None, "Could not contact the server. Error message: " + str(e))
1907 | except Exception:
1908 | return (None, "Could not contact the server.")
1909 |
1910 | jobj = json.loads(response)
1911 |
1912 | if jobj == None or not("result" in jobj) or jobj["result"] == 1:
1913 | if jobj != None:
1914 | return (None, jobj["message"])
1915 | else:
1916 | return (None, "Could not contact the server.")
1917 |
1918 | return (jobj, "")
--------------------------------------------------------------------------------