1
1
import os
2
2
import re
3
3
import sys
4
- from typing import Optional
5
4
5
+ from typing import Optional
6
6
from engineio .payload import Payload
7
7
from flask import Flask , jsonify , request
8
8
from flask_cors import CORS
9
- from flask_jwt_extended import JWTManager , decode_token
9
+ from flask_jwt_extended import JWTManager
10
10
from flask_jwt_extended .exceptions import InvalidHeaderError , NoAuthorizationError
11
11
from flask_restx import Api
12
12
from flask_socketio import SocketIO , join_room
13
- from jwt .exceptions import DecodeError , InvalidSignatureError , InvalidTokenError
13
+ from jwt import decode
14
+ from jwt .exceptions import DecodeError , InvalidSignatureError , InvalidTokenError , ExpiredSignatureError , InvalidKeyError
15
+ from authlib .integrations .flask_client import OAuth
16
+ from authlib .oauth2 .rfc6749 import MissingAuthorizationError
17
+
14
18
15
19
from cnaas_nms .api .device import (
16
20
device_api ,
24
28
device_update_interfaces_api ,
25
29
devices_api ,
26
30
)
31
+ from cnaas_nms .api .auth import api as auth_api
27
32
from cnaas_nms .api .firmware import api as firmware_api
28
33
from cnaas_nms .api .groups import api as groups_api
29
34
from cnaas_nms .api .interface import api as interfaces_api
35
40
from cnaas_nms .api .repository import api as repository_api
36
41
from cnaas_nms .api .settings import api as settings_api
37
42
from cnaas_nms .api .system import api as system_api
43
+
44
+ from cnaas_nms .app_settings import auth_settings
38
45
from cnaas_nms .app_settings import api_settings
46
+
39
47
from cnaas_nms .tools .log import get_logger
40
- from cnaas_nms .tools .security import get_jwt_identity , jwt_required
48
+ from cnaas_nms .tools .security import get_oauth_userinfo
41
49
from cnaas_nms .version import __api_version__
42
50
51
+
43
52
logger = get_logger ()
44
53
45
54
52
61
}
53
62
}
54
63
55
- jwt_query_r = re .compile (r"jwt =[^ &]+" )
64
+ jwt_query_r = re .compile (r"code =[^ &]+" )
56
65
57
66
58
67
class CnaasApi (Api ):
59
68
def handle_error (self , e ):
60
69
if isinstance (e , DecodeError ):
61
- data = {"status" : "error" , "data" : "Could not decode JWT token" }
70
+ data = {"status" : "error" , "message" : "Could not decode JWT token" }
71
+ elif isinstance (e , InvalidKeyError ):
72
+ data = {"status" : "error" , "message" : "Invalid keys {}" .format (e )}
62
73
elif isinstance (e , InvalidTokenError ):
63
- data = {"status" : "error" , "data " : "Invalid authentication header: {}" .format (e )}
74
+ data = {"status" : "error" , "message " : "Invalid authentication header: {}" .format (e )}
64
75
elif isinstance (e , InvalidSignatureError ):
65
- data = {"status" : "error" , "data " : "Invalid token signature" }
76
+ data = {"status" : "error" , "message " : "Invalid token signature" }
66
77
elif isinstance (e , IndexError ):
67
- # We might catch IndexErrors which are not cuased by JWT,
78
+ # We might catch IndexErrors which are not caused by JWT,
68
79
# but this is better than nothing.
69
- data = {"status" : "error" , "data " : "JWT token missing?" }
80
+ data = {"status" : "error" , "message " : "JWT token missing?" }
70
81
elif isinstance (e , NoAuthorizationError ):
71
- data = {"status" : "error" , "data " : "JWT token missing?" }
82
+ data = {"status" : "error" , "message " : "JWT token missing?" }
72
83
elif isinstance (e , InvalidHeaderError ):
73
- data = {"status" : "error" , "data" : "Invalid header, JWT token missing? {}" .format (e )}
84
+ data = {"status" : "error" , "message" : "Invalid header, JWT token missing? {}" .format (e )}
85
+ elif isinstance (e , ExpiredSignatureError ):
86
+ data = {"status" : "error" , "message" : "The JWT token is expired" }
87
+ elif isinstance (e , MissingAuthorizationError ):
88
+ data = {"status" : "error" , "message" : "JWT token missing?" }
89
+ elif isinstance (e , ConnectionError ):
90
+ data = {"status" : "error" , "message" : "ConnectionError: {}" .format (e )}
91
+ return jsonify (data ), 500
74
92
else :
75
93
return super (CnaasApi , self ).handle_error (e )
76
94
return jsonify (data ), 401
77
95
78
96
79
97
app = Flask (__name__ )
98
+
99
+ # To register the OAuth client
100
+ oauth = OAuth (app )
101
+ client = oauth .register (
102
+ "connext" ,
103
+ server_metadata_url = auth_settings .OIDC_CONF_WELL_KNOWN_URL ,
104
+ client_id = auth_settings .OIDC_CLIENT_ID ,
105
+ client_secret = auth_settings .OIDC_CLIENT_SECRET ,
106
+ client_kwargs = {"scope" : auth_settings .OIDC_CLIENT_SCOPE },
107
+ response_type = "code" ,
108
+ response_mode = "query" ,
109
+ )
110
+
80
111
app .config ["RESTX_JSON" ] = {"cls" : CNaaSJSONEncoder }
81
112
82
113
# TODO: make origins configurable
@@ -88,14 +119,16 @@ def handle_error(self, e):
88
119
Payload .max_decode_packets = 500
89
120
socketio = SocketIO (app , cors_allowed_origins = "*" )
90
121
122
+
123
+ if api_settings .JWT_ENABLED or auth_settings .OIDC_ENABLED :
124
+ app .config ["SECRET_KEY" ] = os .urandom (128 )
91
125
if api_settings .JWT_ENABLED :
92
126
try :
93
127
jwt_pubkey = open (api_settings .JWT_CERT ).read ()
94
128
except Exception as e :
95
129
print ("Could not load public JWT cert from api.yml config: {}" .format (e ))
96
130
sys .exit (1 )
97
131
98
- app .config ["SECRET_KEY" ] = os .urandom (128 )
99
132
app .config ["JWT_PUBLIC_KEY" ] = jwt_pubkey
100
133
app .config ["JWT_IDENTITY_CLAIM" ] = "sub"
101
134
app .config ["JWT_ALGORITHM" ] = "ES256"
@@ -108,6 +141,7 @@ def handle_error(self, e):
108
141
app , prefix = "/api/{}" .format (__api_version__ ), authorizations = authorizations , security = "apikey" , doc = "/api/doc/"
109
142
)
110
143
144
+ api .add_namespace (auth_api )
111
145
api .add_namespace (device_api )
112
146
api .add_namespace (devices_api )
113
147
api .add_namespace (device_init_api )
@@ -133,12 +167,28 @@ def handle_error(self, e):
133
167
api .add_namespace (plugins_api )
134
168
api .add_namespace (system_api )
135
169
136
-
137
170
# SocketIO on connect
138
171
@socketio .on ("connect" )
139
- @jwt_required
140
172
def socketio_on_connect ():
141
- user = get_jwt_identity ()
173
+ # get te token string
174
+ token_string = request .args .get ('jwt' )
175
+ if not token_string :
176
+ return False
177
+ #if oidc, get userinfo
178
+ if auth_settings .OIDC_ENABLED :
179
+ try :
180
+ user = get_oauth_userinfo (token_string )['email' ]
181
+ except InvalidTokenError as e :
182
+ logger .debug ('InvalidTokenError: ' + format (e ))
183
+ return False
184
+ # else decode the token and get the sub there
185
+ else :
186
+ try :
187
+ user = decode (token_string , app .config ["JWT_PUBLIC_KEY" ], algorithms = [app .config ["JWT_ALGORITHM" ]])['sub' ]
188
+ except DecodeError as e :
189
+ logger .debug ('DecodeError: ' + format (e ))
190
+ return False
191
+
142
192
if user :
143
193
logger .info ("User: {} connected via socketio" .format (user ))
144
194
return True
@@ -165,18 +215,20 @@ def socketio_on_events(data):
165
215
# Log all requests, include username etc
166
216
@app .after_request
167
217
def log_request (response ):
168
- try :
169
- token = request .headers .get ("Authorization" ).split (" " )[- 1 ]
170
- user = decode_token (token ).get ("sub" )
171
- except Exception :
172
- user = "unknown"
173
218
try :
174
219
url = re .sub (jwt_query_r , "" , request .url )
175
- logger .info (
176
- "User: {}, Method: {}, Status: {}, URL: {}, JSON: {}" .format (
177
- user , request .method , response .status_code , url , request .json
220
+ if request .headers .get ('content-type' ) == 'application/json' :
221
+ logger .info (
222
+ "Method: {}, Status: {}, URL: {}, JSON: {}" .format (
223
+ request .method , response .status_code , url , request .json
224
+ )
225
+ )
226
+ else :
227
+ logger .info (
228
+ "Method: {}, Status: {}, URL: {}" .format (
229
+ request .method , response .status_code , url
230
+ )
178
231
)
179
- )
180
232
except Exception :
181
233
pass
182
234
return response
0 commit comments