From 123d7d1ecf65d0bf982cf63cf145c9ca2c93aa75 Mon Sep 17 00:00:00 2001 From: Jason Secula Date: Wed, 18 Feb 2026 10:58:06 +0100 Subject: [PATCH] Initial commit --- .gitea/workflows/build.yaml | 19 ++ .gitlab-ci.yml | 17 ++ GLPIAPI.py | 336 ++++++++++++++++++++++++++++++++++++ README.md | 23 +++ build/pyproject.toml | 16 ++ build/src/__init__.py | 0 6 files changed, 411 insertions(+) create mode 100644 .gitea/workflows/build.yaml create mode 100644 .gitlab-ci.yml create mode 100644 GLPIAPI.py create mode 100644 README.md create mode 100644 build/pyproject.toml create mode 100644 build/src/__init__.py diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..a73eeb2 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,19 @@ +name: Build python package +run-name: building python package GLPIAPI +on: [push] + +jobs: + Build: + runs-on: windows + steps: + - name: Check out repository code + uses: actions/checkout@main + - name: Building the package + run: | + mv ${{ gitea.workspace }}\GLPIAPI.py ${{ gitea.workspace }}\build\src\GLPIAPI\ + cd ${{ gitea.workspace }}\build + python -m build + - name: Publish package + run: | + python -m twine upload -u ${{ secrets.repo_user }} -p ${{ secrets.repo_pass }} --repository-url ${{ secrets.repo_url }} ${{ gitea.workspace }}\build\dist\* + if: github.ref_type == 'tag' \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..4de85f2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,17 @@ +image: python:latest + +variables: + TWINE_USERNAME: gitlab-ci-token + TWINE_PASSWORD: $CI_JOB_TOKEN + +build: # This job runs in the build stage, which runs first. + script: + - pip install build twine + - mkdir build/src/GLPIAPI/ + - mv GLPIAPI.py build/src/GLPIAPI/ + - cd build/ + - python -m build + - python -m twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/* + rules: + - if: $CI_COMMIT_TAG + \ No newline at end of file diff --git a/GLPIAPI.py b/GLPIAPI.py new file mode 100644 index 0000000..633e141 --- /dev/null +++ b/GLPIAPI.py @@ -0,0 +1,336 @@ +import requests +import json +from datetime import datetime + +class GLPIAPI: + def __init__(self, Server, AppToken, UserToken, UserAgent="GLPI API"): + self.Server = Server + self.AppToken = AppToken + self.UserToken = UserToken + self.UserAgent = UserAgent + self.SessionToken = None + self.StatusCode = None + self.Headers = None + self.InitConnection() + + def InitConnection(self): + initURI = '/apirest.php/initSession/' + GLPIHeaders = { + 'Content-Type': 'application/json', + "Authorization": f"user_token {self.UserToken}", + "App-Token": self.AppToken + } + + # Récupération d'un token de session + uri = f"{self.Server}{initURI}" + result = requests.get(uri, headers=GLPIHeaders) + self.StatusCode = result.status_code + if(result.status_code == 200): + self.SessionToken = result.json()["session_token"] + self.Headers = { + 'Content-Type': 'application/json', + "Session-Token": self.SessionToken, + "App-Token": self.AppToken + } + + def GetDevice(self, airwatchDevice=None, serialNumber=None, imei=None): + if(airwatchDevice != None): + if(airwatchDevice.Imei != ''): + # Recherche des appareils en fonction du numéro de série ou de l'imei + # l'imei pouvant être dans le champ numéro de série ou les champs imei custom (plugin field) + search_parameter = f'is_deleted=0&criteria[0][field]=5&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{airwatchDevice.SerialNumber}$'\ + f'&criteria[1][link]=OR&criteria[1][field]=5&criteria[1][searchtype]=contains&criteria[1][value]=^{airwatchDevice.Imei}$'\ + f'&criteria[2][link]=OR&criteria[2][field]=76667&criteria[2][searchtype]=contains&criteria[2][value]=^{airwatchDevice.Imei}$'\ + f'&criteria[3][link]=OR&criteria[3][field]=76670&criteria[3][searchtype]=contains&criteria[3][value]=^{airwatchDevice.Imei}$' + else: + # Recherche des appareils en fonction du numéro de série seulement + search_parameter = f'is_deleted=0&criteria[0][field]=5&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{airwatchDevice.SerialNumber}$' + else: + if(imei != None): + # Recherche des appareils en fonction du numéro de série ou de l'imei + # l'imei pouvant être dans le champ numéro de série ou les champs imei custom (plugin field) + search_parameter = f'is_deleted=0&criteria[0][field]=5&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{serialNumber}$'\ + f'&criteria[1][link]=OR&criteria[1][field]=5&criteria[1][searchtype]=contains&criteria[1][value]=^{imei}$'\ + f'&criteria[2][link]=OR&criteria[2][field]=76667&criteria[2][searchtype]=contains&criteria[2][value]=^{imei}$'\ + f'&criteria[3][link]=OR&criteria[3][field]=76670&criteria[3][searchtype]=contains&criteria[3][value]=^{imei}$' + else: + # Recherche des appareils en fonction du numéro de série seulement + search_parameter = f'is_deleted=0&criteria[0][field]=5&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{serialNumber}$' + + searchUri = f"{self.Server}/apirest.php/search/Computer?{search_parameter}" + search = requests.get(searchUri, headers=self.Headers) + if(search.status_code == 200): + search = search.json() + if(search["totalcount"] == 1): + deviceID = list(search["data"].keys())[0] + data = search["data"][deviceID] + + return deviceID, data, search["totalcount"] + elif(search["totalcount"] > 1): + deviceID = list(search["data"].keys()) + return deviceID, search["data"], search["totalcount"] + + return None, None, 0 + + def GetUser(self, username=None, email=None): + + if(username != None): + search_parameter = f'is_deleted=0&criteria[0][field]=1&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{username}$' + elif(email != None): + search_parameter = f'is_deleted=0&criteria[0][field]=5&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{email}$' + searchUri = f"{self.Server}/apirest.php/search/user?{search_parameter}" + search = requests.get(searchUri, headers=self.Headers) + if(search.status_code == 200): + search = search.json() + if(search["totalcount"] == 1): + userID = list(search["data"].keys())[0] + data = search["data"][userID] + + return userID, data, search["totalcount"] + elif(search["totalcount"] > 1): + userID = list(search["data"].keys()) + return userID, search["data"], search["totalcount"] + + return None, None, 0 + + def UploadFile(self, file, path): + manifest = { + "input": { + "name": file, + "is_recursive": 1, + "_filename": [file], + "documentcategories_id": 4 + } + } + fileData = open(path+file, "rb") + headers = { + "Session-Token": self.SessionToken, + "App-Token": self.AppToken + } + data = {"uploadManifest": json.dumps(manifest)} + files = {file: fileData} + return requests.post(self.Server+"/apirest.php/Document/", headers=headers, data=data, files=files) + + def SetDocumentToDevice(self, deviceID, documentID): + body = { + "input": { + "documents_id": documentID, + "items_id": deviceID, + "itemtype": "Computer" + } + } + return requests.post(self.Server+"/apirest.php/Document/"+str(documentID)+"/Document_Item", headers=self.Headers, json=body) + + def UpdateInventory(self, inventory): + headers = { + "Content-Type":"Application/x-compress", + "user-agent":self.UserAgent + } + return requests.post(self.Server, headers=headers, data=inventory) + + def UpdateSerialNumber(self, deviceid, serialnumber): + + body = { + "input" : { + "id" : deviceid, + "serial" : serialnumber + } + } + uri = f"{self.Server}/apirest.php/Computer/" + return requests.put(uri, headers=self.Headers, json=body) + + def UpdateUser(self, deviceid, username): + + body = { + "input" : { + "id" : deviceid, + "users_id" : username + } + } + uri = f"{self.Server}/apirest.php/Computer/" + return requests.put(uri, headers=self.Headers, json=body) + + def SetField(self, itemType, containerName, containerID, itemId, fieldName, data): + '''Requires fields plugin on GLPI server + - containerName is block label name + - containerID is block id + ''' + uri = f"{self.Server}/apirest.php/PluginFields{itemType}{containerName}" + searchURI = f"{self.Server}/apirest.php/PluginFields{itemType}{containerName}?range=0-999999999" + result = requests.get(searchURI, headers=self.Headers) + if(result.status_code == 200): + result = result.json() + fieldItem = None + # searching for field item + for entry in result: + if str(entry["items_id"]) == itemId: + fieldItem = entry + + if(fieldItem == None): + body = { + "input": { + "items_id": itemId, + "itemtype": itemType, + "plugin_fields_containers_id": containerID, + fieldName: data + } + } + return requests.post(uri, headers=self.Headers, json=body) + else: + body = { + "input": { + "id": fieldItem["id"], + fieldName: data + } + } + return requests.put(uri, headers=self.Headers, json=body) + + def CreateInventoryForAirwatchDevice(self, device, deviceName, apps=None): + platforms = { + 2:"Apple iOS", + 5:"Android", + 10:"Apple macOS", + 12:"Windows" + } + + if(device.PlatformId in platforms.keys()): + platformName = platforms[device.PlatformId] + else: + platformName = "Unknown" + + processorArchs = { + 0:{ + "osArch":"arm64", + "softwareArch":"arm64" + }, + 9:{ + "osArch":"64-bit", + "softwareArch":"x86_64" + } + } + if(device.Arch in processorArchs.keys()): + osArch = processorArchs[device.Arch]["osArch"] + softwareArch = processorArchs[device.Arch]["softwareArch"] + else: + osArch = "Unknown" + softwareArch = "Unknown" + + windowsOSTranslation = { + "10.0.19043":"10 21H1", + "10.0.19044":"10 21H2", + "10.0.19045":"10 22H2", + "10.0.22000":"11 21H2", + "10.0.22621":"11 22H2", + "10.0.22631":"11 23H2", + "10.0.26100":"11 24H2", + "10.0.26200":"11 25H2" + } + + logDate = datetime.strptime(device.LastSeen, "%Y-%m-%dT%H:%M:%S.%f").strftime("%Y-%m-%d %H:%M:%S") + + inventory = GLPIInventory(logdate=logDate, versionclient=self.UserAgent, tag=device.Group, deviceid=f"{deviceName} - {device.SerialNumber}", itemtype="Computer") + if(platformName == "Windows"): + if(device.OS in windowsOSTranslation.keys()): + inventory.SetOperatingSystem(platformName, windowsOSTranslation[str(device.OS)], osArch) + else: + platformName = "Windows Desktop" + inventory.SetOperatingSystem(platformName, device.OS, osArch) + else: + inventory.SetOperatingSystem(platformName, device.OS, osArch) + inventory.SetHardware(deviceName, device.Uuid, device.TotalMemory) + inventory.AddUser(device.User) + + if(apps != None): + for app in apps: + if(app.Status != "Installed"): + continue + + install_date = datetime.strptime(app.InstallDate, "%Y-%m-%dT%H:%M:%S.%f").strftime("%Y-%m-%d") + + if(install_date == "1-01-01"): + inventory.AddSoftware(app.Name, app.Version, app.Size, softwareArch, app.Guid) + else: + inventory.AddSoftware(app.Name, app.Version, app.Size, softwareArch, app.Guid, install_date) + + return inventory + +class GLPIInventory: + def __init__(self, logdate=None, versionclient=None, tag=None, deviceid=None, itemtype=None): + self.logdate = logdate + self.versionclient = versionclient + self.users = [] + self.operatingsystem = {} + self.softwares = [] + self.hardware = {} + self.tag = tag + self.deviceId = deviceid + self.itemType = itemtype + + + def AddUser(self, user): + self.users += [{ + "login": user + }] + + def DelUser(self, user): + for i in range(0, len(self.users)): + if(self.users[i]["login"] == user): + del self.users[i] + + def AddSoftware(self, name, version, filesize, arch, guid, install_date=None): + if(install_date == None): + self.softwares += [{ + "name": name, + "guid": guid, + "version": version, + "filesize": filesize, + "arch": arch + }] + else: + self.softwares += [{ + "name": name, + "guid": guid, + "version": version, + "install_date": install_date, + "filesize": filesize, + "arch": arch + }] + + def DelSoftware(self, guid): + for i in range(0, len(self.softwares)): + if(self.softwares[i]["guid"] == guid): + del self.softwares[i] + + def SetHardware(self, name, uuid, memory): + self.hardware = { + "name": name, + "uuid": uuid, + "memory": memory + } + + def SetOperatingSystem(self, name, version, arch): + self.operatingsystem = { + "name": name, + "version": version, + "full_name": f"{name} {version}", + "arch": arch + } + + def Json(self): + inventory = { + "action": "inventory", + "content":{ + "accesslog":{ + "logdate": self.logdate + }, + "versionclient": self.versionclient, + "users": self.users, + "operatingsystem": self.operatingsystem, + "softwares": self.softwares, + "hardware": self.hardware + }, + "tag": self.tag, + "deviceid": self.deviceId, + "itemtype": self.itemType + } + return json.dumps(inventory) diff --git a/README.md b/README.md new file mode 100644 index 0000000..feb1c2e --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# GLPIAPI + +A python module to help making API requests to GLPI servers easier. + +Usage +================ + +You can create an GLPIAPI object to connect to the API using an APpToken and an UserToken. + +Example : + +```python +from GLPIAPI import GLPIAPI + +glpiServer = https://my-glpi-server.local +glpiAppToken = "YOUR-APP-TOKEN" +glpiUserToken = "YOUR-USER-TOKEN" +glpiUserAgent = "GLPI API Connector" + +GLPIConnector = GLPIAPI(Server=glpiServer, AppToken=glpiAppToken, UserToken=glpiUserToken, UserAgent=glpiUserAgent) + +device = GLPIConnector.GetDevice(serialNumber="S0123456789") +``` diff --git a/build/pyproject.toml b/build/pyproject.toml new file mode 100644 index 0000000..0b463d6 --- /dev/null +++ b/build/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = [ + "setuptools >= 77.0.3", + "requests >= 2.32.5" +] +build-backend = "setuptools.build_meta" + +[project] +name = "GLPIAPI" +version = "1.0.0" +description = "A module python to make it easier to use GLPI API" +readme = "README.md" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python :: 3" +] diff --git a/build/src/__init__.py b/build/src/__init__.py new file mode 100644 index 0000000..e69de29