Skip to content

Commit d9a10eb

Browse files
author
Maximilian Flügel
committed
Merge remote-tracking branch 'origin/main'
# Conflicts: # .idea/workspace.xml
2 parents 36d82b8 + 8667427 commit d9a10eb

14 files changed

+639
-27
lines changed

.idea/workspace.xml

Lines changed: 166 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,52 @@
11
# iot-utilities-server-python
2-
Python WebSocket Server implementation that works just like the IoT-Utilities App
2+
Python WebSocket Server implementation that works like the IoT-Utilities Android App.
3+
4+
## Usage
5+
6+
- Download or clone the source code
7+
- Use your terminal to navigate into the folder of the project
8+
- Install the dependencies
9+
10+
```
11+
pip3 install -r requirements.txt
12+
```
13+
14+
- Make sure python 3 is installed, start the server:
15+
16+
```
17+
python3 -m bin.main
18+
```
19+
20+
The server implementation contains both an authentication server (default on port 5444) and a WebSocket server (default on port 5443).
21+
22+
## Authentication Configuration
23+
24+
For security reasons, the authentication credentials of the server have to be configured seperately.
25+
Therefore, edit the "config.json" file and enter the desired credentials for the server.
26+
27+
- auth_username: Username required when using grant type "password"
28+
- auth_password: Password required when using grant type "password"
29+
- auth_secret: Secret required when using grant type "client_credentials"
30+
- jwt_signature_secret: Secret used to sign and verify the JWT access tokens
31+
32+
Example:
33+
34+
```json
35+
{
36+
"auth_username": "<place_your_username_here>",
37+
"auth_password": "<place_your_password_here>",
38+
"auth_secret": "<place_your_secret_here>",
39+
"jwt_signature_secret": "<place_your_secret_here>"
40+
}
41+
```
42+
43+
## Command line arguments
44+
45+
All command line arguments and explanations can be listed using the --help argument.
46+
47+
--port: Port of the WebSocket server
48+
--authport: Port of the Authentication server
49+
--certificate: Path to the certificate PEM file
50+
--raw: Prints the messages in JSON format if present
51+
52+
> Note: The server supports both HTTP/WS and HTTPS/WSS connections. In order to encrypt the connection, a SSL certificate file (PEM format) needs to be provided using the --certificate command line argument. Example: python3 -m bin.main --certificate "test.pem"

bin/main.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from src.endpoint.iot_endpoint_server import IoTEndpointServer
2+
from src.endpoint.iot_endpoint_authentication_server import IoTEndpointAuthenticationServer
23
import argparse
4+
from threading import Thread
35

46

57
def main():
@@ -14,13 +16,28 @@ def main():
1416
parser.add_argument("--raw", action="store_true", help="Enables/disables raw protobuf message output")
1517
arguments = parser.parse_args()
1618

17-
# Configure and start the WebSocket server
19+
# Configure the WebSocket server
1820
server = IoTEndpointServer(
1921
arguments.port if arguments.port else 5443,
2022
arguments.certificate,
2123
arguments.raw
2224
)
23-
server.start()
25+
26+
# Configure the authentication server
27+
authentication_server = IoTEndpointAuthenticationServer(
28+
arguments.certificate
29+
)
30+
31+
# Start the threads for the servers
32+
https_thread = Thread(target=authentication_server.start)
33+
https_thread.start()
34+
35+
wss_thread = Thread(target=server.start)
36+
wss_thread.start()
37+
38+
# Join the threads to the main thread
39+
wss_thread.join()
40+
https_thread.join()
2441

2542

2643
if __name__ == "__main__":

config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"auth_username": "USERNAME_HERE",
3+
"auth_password": "PASSWORD_HERE",
4+
"auth_secret": "AUTH_SECRET_HERE",
5+
"jwt_signature_secret": "JWT_SECRET_HERE"
6+
}

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
protobuf==3.20.1
2+
websockets~=10.3
3+
PyJWT~=2.6.0

src/authentication/__init__.py

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AuthenticationException(Exception):
2+
"""
3+
Class that represents an exception that occurred during the authentication procedure of the program.
4+
"""
5+
pass
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import json
2+
from src.authentication.authentication_exception import AuthenticationException
3+
from src.authentication.jwt_authenticator import JWTAuthenticator
4+
from src.configuration import config
5+
6+
7+
class ClientAuthenticationRequest:
8+
"""
9+
Class that represents a single authentication request.
10+
"""
11+
12+
GRANT_TYPE_PASSWORD = "password"
13+
GRANT_TYPE_REFRESH_TOKEN = "refresh_token"
14+
GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"
15+
16+
def __init__(
17+
self,
18+
grant_type=None,
19+
client_id=None,
20+
client_secret=None,
21+
username=None,
22+
password=None,
23+
refresh_token=None,
24+
scope=None):
25+
"""
26+
Constructor of the instance.
27+
28+
:param grant_type: Type of authentication used during the procedure.
29+
:type grant_type: str
30+
:param client_id: Identifier of the authenticating client.
31+
:type client_id: str
32+
:param client_secret: Secret phrase used when authenticating with grant type "client-secret".
33+
:type client_secret: str
34+
:param username: Username used when authenticating with grant type "password".
35+
:type username: str
36+
:param password: Password used when authenticating with grant type "password".
37+
:type password: str
38+
:param refresh_token: Refresh token used when authenticating with grant type "refresh_token".
39+
:type refresh_token: str
40+
:param scope: Scope used by the client, e.g. Aruba.
41+
:type scope: str
42+
"""
43+
self.grant_type = grant_type
44+
self.client_id = client_id
45+
self.client_secret = client_secret
46+
self.username = username
47+
self.password = password
48+
self.refresh_token = refresh_token
49+
self.scope = scope
50+
51+
@staticmethod
52+
def parse_from_json(json_string: str):
53+
"""
54+
Function that parses an instance from a JSON string.
55+
56+
:param json_string: JSON string the instance should be parsed from.
57+
:type json_string: str
58+
59+
:return: Returns the created instance.
60+
"""
61+
loads = json.loads(json_string)
62+
return ClientAuthenticationRequest(**loads)
63+
64+
def validate(self):
65+
"""
66+
Method that validates the parsed authentication data.
67+
Depending on the grant type, the authentication requires additional field:
68+
password: requires the fields username and password.
69+
refresh_token: requires the field refresh_token.
70+
client_credentials: requires the field client_secret.
71+
72+
The remaining fields are optional: client_id, scope.
73+
"""
74+
if not self.grant_type:
75+
raise AuthenticationException("Parameter 'grant_type' missing")
76+
77+
if self.grant_type == self.GRANT_TYPE_PASSWORD:
78+
if not self.username or not self.password:
79+
raise AuthenticationException("Parameter 'username' or 'password' missing")
80+
elif self.grant_type == self.GRANT_TYPE_REFRESH_TOKEN:
81+
if not self.refresh_token:
82+
raise AuthenticationException("Parameter 'refresh_token' missing")
83+
elif self.grant_type == self.GRANT_TYPE_CLIENT_CREDENTIALS:
84+
if not self.client_secret:
85+
raise AuthenticationException("Parameter 'client_secret' missing")
86+
else:
87+
raise AuthenticationException(f"Unknown grant type '{self.grant_type}'")
88+
89+
def verify(self) -> bool:
90+
"""
91+
Function that verifies the authentication credentials.
92+
Depending on the authentication type, the credentials are verified.
93+
94+
:return: Returns whether the provided credentials are valid.
95+
"""
96+
if self.grant_type == self.GRANT_TYPE_PASSWORD:
97+
return self.username == config.auth_username and self.password == config.auth_password
98+
elif self.grant_type == self.GRANT_TYPE_REFRESH_TOKEN:
99+
return JWTAuthenticator.validate_refresh_token(self.refresh_token)
100+
elif self.grant_type == self.GRANT_TYPE_CLIENT_CREDENTIALS:
101+
return self.client_secret == config.auth_secret
102+
else:
103+
raise AuthenticationException(f"Unknown grant type '{self.grant_type}'")

0 commit comments

Comments
 (0)