brain2-oc 2.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brain/__init__.py +0 -0
- brain/__main__.py +56 -0
- brain/define/.git +1 -0
- brain/define/README.md +2 -0
- brain/define/errors.json +8 -0
- brain/define/key.json +27 -0
- brain/define/permission.json +25 -0
- brain/define/user.json +72 -0
- brain/define/verify.json +21 -0
- brain/errors.py +35 -0
- brain/helpers/__init__.py +0 -0
- brain/helpers/access.py +275 -0
- brain/helpers/users.py +171 -0
- brain/install.py +201 -0
- brain/records/__init__.py +25 -0
- brain/records/key.py +72 -0
- brain/records/permission.py +226 -0
- brain/records/user.py +312 -0
- brain/rest.py +145 -0
- brain/rights.py +29 -0
- brain/service.py +3497 -0
- brain/upgrade.py +58 -0
- brain/upgrades/__init__.py +0 -0
- brain/upgrades/v1_0_1_v1_1_0.py +97 -0
- brain/upgrades/v1_1_0_v1_1_1.py +79 -0
- brain/upgrades/v1_1_1_v2_2_0.py +94 -0
- brain/upgrades/v2_2_0_v2_3_0.py +207 -0
- brain2_oc-2.4.0.dist-info/METADATA +859 -0
- brain2_oc-2.4.0.dist-info/RECORD +32 -0
- brain2_oc-2.4.0.dist-info/WHEEL +4 -0
- brain2_oc-2.4.0.dist-info/entry_points.txt +2 -0
- brain2_oc-2.4.0.dist-info/licenses/LICENSE +8 -0
brain/__init__.py
ADDED
|
File without changes
|
brain/__main__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
""" Brain
|
|
3
|
+
|
|
4
|
+
Handles authorization / user requests
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__author__ = "Chris Nasr"
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__copyright__ = "Ouroboros Coding Inc."
|
|
10
|
+
__email__ = "chris@ouroboroscoding.com"
|
|
11
|
+
__created__ = "2022-08-25"
|
|
12
|
+
|
|
13
|
+
# Python imports
|
|
14
|
+
from sys import argv, exit, stderr
|
|
15
|
+
|
|
16
|
+
def cli():
|
|
17
|
+
"""CLI
|
|
18
|
+
|
|
19
|
+
Called from the command line to run from the current directory
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
uint
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# If we have no arguments
|
|
26
|
+
if len(argv) == 1:
|
|
27
|
+
|
|
28
|
+
# Run the REST server
|
|
29
|
+
from brain import rest
|
|
30
|
+
return rest.run()
|
|
31
|
+
|
|
32
|
+
# Else, if we have one argument
|
|
33
|
+
elif len(argv) == 2:
|
|
34
|
+
|
|
35
|
+
# If we are installing
|
|
36
|
+
if argv[1] == 'install':
|
|
37
|
+
from brain import install
|
|
38
|
+
return install.run()
|
|
39
|
+
|
|
40
|
+
# Else, if we are explicitly stating the rest service
|
|
41
|
+
elif argv[1] == 'rest':
|
|
42
|
+
from brain import rest
|
|
43
|
+
return rest.run()
|
|
44
|
+
|
|
45
|
+
# Else, if we are upgrading
|
|
46
|
+
elif argv[1] == 'upgrade':
|
|
47
|
+
from brain import upgrade
|
|
48
|
+
return upgrade.run()
|
|
49
|
+
|
|
50
|
+
# Else, arguments are wrong, print and return an error
|
|
51
|
+
print('Invalid arguments', file = stderr)
|
|
52
|
+
return 1
|
|
53
|
+
|
|
54
|
+
# Only run if called directly
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
exit(cli())
|
brain/define/.git
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: ../../.git/modules/brain/define
|
brain/define/README.md
ADDED
brain/define/errors.json
ADDED
brain/define/key.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_id": {
|
|
3
|
+
"__type__": "string",
|
|
4
|
+
"__regex__": "^[0-9a-f]{32}$",
|
|
5
|
+
"__minimum__": 32,
|
|
6
|
+
"__maximum__": 32
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"_created": {
|
|
10
|
+
"__type__": "timestamp",
|
|
11
|
+
"__optional__": true
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
"_updated": {
|
|
15
|
+
"__type__": "timestamp",
|
|
16
|
+
"__optional__": true
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
"user": {
|
|
20
|
+
"__type__": "tuuid"
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
"type": {
|
|
24
|
+
"__type__": "string",
|
|
25
|
+
"__options__": [ "forgot", "setup", "verify" ]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_user": {
|
|
3
|
+
"__type__": "tuuid"
|
|
4
|
+
},
|
|
5
|
+
|
|
6
|
+
"_portal": {
|
|
7
|
+
"__type__": "string",
|
|
8
|
+
"__maximum__": 16
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
"name": {
|
|
12
|
+
"__type__": "string",
|
|
13
|
+
"__regex__": "[a-z_]{1,32}"
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"id": {
|
|
17
|
+
"__type__": "tuuid"
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
"rights": {
|
|
21
|
+
"__type__": "uint",
|
|
22
|
+
"__minimum__": 1,
|
|
23
|
+
"__maximum__": 15
|
|
24
|
+
}
|
|
25
|
+
}
|
brain/define/user.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_id": {
|
|
3
|
+
"__type__": "tuuid",
|
|
4
|
+
"__optional__": true
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
"_created": {
|
|
8
|
+
"__type__": "timestamp",
|
|
9
|
+
"__optional__": true
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
"_updated": {
|
|
13
|
+
"__type__": "timestamp",
|
|
14
|
+
"__optional__": true
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"email": {
|
|
18
|
+
"__type__": "string",
|
|
19
|
+
"__maximum__": 127
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"passwd": {
|
|
23
|
+
"__type__":"string",
|
|
24
|
+
"__regex__":"^[0-9a-fA-F]{72}$"
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"locale": {
|
|
28
|
+
"__type__": "string",
|
|
29
|
+
"__regex__": "^[a-z]{2}-[A-Z]{2}$"
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
"first_name": {
|
|
33
|
+
"__type__": "string",
|
|
34
|
+
"__minumum__": 1,
|
|
35
|
+
"__maximum__": 31
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
"last_name": {
|
|
39
|
+
"__type__": "string",
|
|
40
|
+
"__minumum__": 2,
|
|
41
|
+
"__maximum__": 31
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
"title": {
|
|
45
|
+
"__type__": "string",
|
|
46
|
+
"__maximum__": 31,
|
|
47
|
+
"__optional__": true
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
"suffix": {
|
|
51
|
+
"__type__": "string",
|
|
52
|
+
"__maximum__": 31,
|
|
53
|
+
"__optional__": true
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
"phone_number": {
|
|
57
|
+
"__type__": "string",
|
|
58
|
+
"__regex__": "^(\\+[\\d \\(\\)-]{10,30}|[\\d \\(\\)-]{10,31})$",
|
|
59
|
+
"__optional__": true
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
"phone_ext": {
|
|
63
|
+
"__type__": "string",
|
|
64
|
+
"__maximum__": 11,
|
|
65
|
+
"__optional__": true
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
"verified": {
|
|
69
|
+
"__type__": "bool",
|
|
70
|
+
"__optional__": true
|
|
71
|
+
}
|
|
72
|
+
}
|
brain/define/verify.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": {
|
|
3
|
+
"__type__": "string"
|
|
4
|
+
},
|
|
5
|
+
"right": [ {
|
|
6
|
+
"__type__": "uint",
|
|
7
|
+
"__options__": [ 1, 2, 4, 8 ]
|
|
8
|
+
}, {
|
|
9
|
+
"__array__": "unique",
|
|
10
|
+
"__type__": "uint",
|
|
11
|
+
"__options__": [ 1, 2, 4, 8 ]
|
|
12
|
+
} ],
|
|
13
|
+
"id": [ {
|
|
14
|
+
"__type__": "string",
|
|
15
|
+
"__optional__": true
|
|
16
|
+
}, {
|
|
17
|
+
"__array__": "unique",
|
|
18
|
+
"__type__": "string",
|
|
19
|
+
"__optional__": true
|
|
20
|
+
} ]
|
|
21
|
+
}
|
brain/errors.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
""" Errors
|
|
3
|
+
|
|
4
|
+
Brain error codes
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__author__ = "Chris Nasr"
|
|
8
|
+
__copyright__ = "Ouroboros Coding Inc"
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__email__ = "chris@ouroboroscoding.com"
|
|
11
|
+
__created__ = "2023-01-16"
|
|
12
|
+
|
|
13
|
+
# Import all body errors as local errors
|
|
14
|
+
from body.errors import *
|
|
15
|
+
|
|
16
|
+
SIGNIN_FAILED = 1200
|
|
17
|
+
"""Sign In Failed"""
|
|
18
|
+
|
|
19
|
+
PASSWORD_STRENGTH = 1201
|
|
20
|
+
"""Password not strong enough"""
|
|
21
|
+
|
|
22
|
+
BAD_PORTAL = 1202
|
|
23
|
+
"""Portal doesn't exist, or the user doesn't have permissions for it"""
|
|
24
|
+
|
|
25
|
+
INTERNAL_KEY = 1203
|
|
26
|
+
"""Internal key failed to validate"""
|
|
27
|
+
|
|
28
|
+
BAD_OAUTH = 1204
|
|
29
|
+
"""Something failed in the OAuth process"""
|
|
30
|
+
|
|
31
|
+
BAD_CONFIG = 1205
|
|
32
|
+
"""Something is missing from the configuration"""
|
|
33
|
+
|
|
34
|
+
__all__ = [ n for n,v in globals().items() if isinstance(v, int) ]
|
|
35
|
+
""" Export all the constants"""
|
|
File without changes
|
brain/helpers/access.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
""" Access
|
|
3
|
+
|
|
4
|
+
Shared methods for verifying access
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__author__ = "Chris Nasr"
|
|
8
|
+
__copyright__ = "Ouroboros Coding Inc"
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__email__ = "chris@ouroboroscoding.com"
|
|
11
|
+
__created__ = "2022-08-29"
|
|
12
|
+
|
|
13
|
+
# Limit exports
|
|
14
|
+
__all__ = [
|
|
15
|
+
'generate_key', 'internal', 'internal_or_verify', 'SYSTEM_USER_ID' 'verify'
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Ouroboros imports
|
|
19
|
+
import body
|
|
20
|
+
from config import config
|
|
21
|
+
import jobject
|
|
22
|
+
from memory import _Memory
|
|
23
|
+
from nredis import nr
|
|
24
|
+
from strings import random
|
|
25
|
+
|
|
26
|
+
# Python imports
|
|
27
|
+
from hashlib import sha1
|
|
28
|
+
from time import time
|
|
29
|
+
from typing import List, MutableMapping
|
|
30
|
+
from sys import stderr
|
|
31
|
+
|
|
32
|
+
# Package imports
|
|
33
|
+
from brain import errors, rights
|
|
34
|
+
|
|
35
|
+
# Constants
|
|
36
|
+
INTERNAL = 1
|
|
37
|
+
VERIFY = 2
|
|
38
|
+
|
|
39
|
+
ALL = rights.ALL
|
|
40
|
+
A = ALL
|
|
41
|
+
"""Allowed to CRUD"""
|
|
42
|
+
|
|
43
|
+
CREATE = rights.CREATE
|
|
44
|
+
C = CREATE
|
|
45
|
+
"""Allowed to create records"""
|
|
46
|
+
|
|
47
|
+
DELETE = rights.DELETE
|
|
48
|
+
D = DELETE
|
|
49
|
+
"""Allowed to delete records"""
|
|
50
|
+
|
|
51
|
+
READ = rights.READ
|
|
52
|
+
R = READ
|
|
53
|
+
"""Allowed to read records"""
|
|
54
|
+
|
|
55
|
+
UPDATE = rights.UPDATE
|
|
56
|
+
U = UPDATE
|
|
57
|
+
"""Allowed to update records"""
|
|
58
|
+
|
|
59
|
+
SYSTEM_USER_ID = '00000000000000000000000000000000'
|
|
60
|
+
"""System User ID"""
|
|
61
|
+
|
|
62
|
+
RIGHTS_ALL_ID = '012345679abc4defa0123456789abcde'
|
|
63
|
+
"""Used to represent rights across the entire system"""
|
|
64
|
+
|
|
65
|
+
_internal = config.brain.internal({
|
|
66
|
+
'redis': 'session',
|
|
67
|
+
'salt': '',
|
|
68
|
+
'ttl': 5
|
|
69
|
+
})
|
|
70
|
+
"""Internal Configuration"""
|
|
71
|
+
|
|
72
|
+
_redis_conn = nr(_internal['redis'])
|
|
73
|
+
"""Redis Connection"""
|
|
74
|
+
|
|
75
|
+
# Encode the salt to utf-8
|
|
76
|
+
_internal['salt'] = _internal['salt'].encode('utf-8')
|
|
77
|
+
|
|
78
|
+
# Don't allow 0 for ttl, and give a warning if anyone tries
|
|
79
|
+
if _internal['ttl'] <= 0:
|
|
80
|
+
print(
|
|
81
|
+
'brain.internal.ttl can NOT be less than 0 (zero). Setting to 5 ' \
|
|
82
|
+
'(seconds)',
|
|
83
|
+
file=stderr
|
|
84
|
+
)
|
|
85
|
+
_internal['ttl'] = 5
|
|
86
|
+
|
|
87
|
+
def generate_key(req: MutableMapping) -> MutableMapping:
|
|
88
|
+
"""Generate Key
|
|
89
|
+
|
|
90
|
+
Used as a wrapper to generates a key and add it to the passed req which it
|
|
91
|
+
then returns.
|
|
92
|
+
|
|
93
|
+
Arguments:
|
|
94
|
+
req (MutableMapping): The dict or jobject being sent with the request.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The same req object passed to it
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
# Pull in the global internal config
|
|
101
|
+
global _internal
|
|
102
|
+
|
|
103
|
+
# Generate a unique ID
|
|
104
|
+
sID = str(random(32, [ 'aZ', '10', '!*' ]))
|
|
105
|
+
|
|
106
|
+
# Get the current timestamp as a string
|
|
107
|
+
sTime = str(int(time()))
|
|
108
|
+
|
|
109
|
+
# Generate a sha1 from the salt and parts of the time
|
|
110
|
+
sSHA1 = sha1(
|
|
111
|
+
sTime[3:].encode('utf-8') +
|
|
112
|
+
_internal['salt'] +
|
|
113
|
+
sTime[:3].encode('utf-8')
|
|
114
|
+
).hexdigest()
|
|
115
|
+
|
|
116
|
+
# Store the key
|
|
117
|
+
if not _redis_conn.set(name = sID, value = sSHA1, ex = _internal['ttl']):
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Add the meta
|
|
121
|
+
try:
|
|
122
|
+
req['meta']['Authorize-Internal'] = '%s~%s' % ( sID, sTime )
|
|
123
|
+
except KeyError:
|
|
124
|
+
req['meta'] = { 'Authorize-Internal': '%s~%s' % ( sID, sTime ) }
|
|
125
|
+
|
|
126
|
+
# Return the req
|
|
127
|
+
return req
|
|
128
|
+
|
|
129
|
+
def internal(req: jobject):
|
|
130
|
+
"""Internal
|
|
131
|
+
|
|
132
|
+
Checks for an internal key and throws an exception if it's missing or
|
|
133
|
+
invalid
|
|
134
|
+
|
|
135
|
+
Arguments:
|
|
136
|
+
req (jobject): The req object passed to the request
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
ResponseException
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
None
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# Pull in the global internal config
|
|
146
|
+
global _internal
|
|
147
|
+
|
|
148
|
+
# Get the current time stamp as soon as possible
|
|
149
|
+
iNow = int(time())
|
|
150
|
+
|
|
151
|
+
# Use bottle from inside body.rest to get the text ID and time using the
|
|
152
|
+
# Authorize-Internal header
|
|
153
|
+
try:
|
|
154
|
+
sID, sTime = req.meta['Authorize-Internal'].split('~')
|
|
155
|
+
except KeyError:
|
|
156
|
+
raise body.ResponseException(error = (
|
|
157
|
+
errors.INTERNAL_KEY, 'missing'
|
|
158
|
+
))
|
|
159
|
+
|
|
160
|
+
# Fetch the key from redis
|
|
161
|
+
sKey = _redis_conn.get(sID).decode()
|
|
162
|
+
if sKey is None:
|
|
163
|
+
raise body.ResponseException(error = (
|
|
164
|
+
errors.INTERNAL_KEY, 'no key'
|
|
165
|
+
))
|
|
166
|
+
|
|
167
|
+
# If the time is not close enough, the rest is irrelevant
|
|
168
|
+
if int(sTime) - iNow > _internal['ttl']:
|
|
169
|
+
|
|
170
|
+
# Raise an exception
|
|
171
|
+
raise body.ResponseException(error = (
|
|
172
|
+
errors.INTERNAL_KEY, 'expired'
|
|
173
|
+
))
|
|
174
|
+
|
|
175
|
+
# Generate a sha1 from the salt, parts of the text, and the time
|
|
176
|
+
sSHA1 = sha1(
|
|
177
|
+
sTime[3:].encode('utf-8') +
|
|
178
|
+
_internal['salt'] +
|
|
179
|
+
sTime[:3].encode('utf-8')
|
|
180
|
+
).hexdigest()
|
|
181
|
+
|
|
182
|
+
# If they aren't equal
|
|
183
|
+
if sSHA1 != sKey:
|
|
184
|
+
raise body.ResponseException(error = (
|
|
185
|
+
errors.INTERNAL_KEY, 'invalid'
|
|
186
|
+
))
|
|
187
|
+
|
|
188
|
+
# Delete the key
|
|
189
|
+
_redis_conn.delete(sID)
|
|
190
|
+
|
|
191
|
+
# Return OK
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
def internal_or_verify(
|
|
195
|
+
req: jobject,
|
|
196
|
+
permission: dict | List[dict]
|
|
197
|
+
) -> str:
|
|
198
|
+
""" Internal or Verify
|
|
199
|
+
|
|
200
|
+
Checks for an internal key, if it wasn't sent, does a verify check.
|
|
201
|
+
|
|
202
|
+
Returns the UUID of the user requesting access via the session, else the \
|
|
203
|
+
SYSTEM_USER_ID when the request is made internally
|
|
204
|
+
|
|
205
|
+
Arguments:
|
|
206
|
+
req (jobject): The current request object sent to the request
|
|
207
|
+
permission (dict | dict[]): A dict with 'name', 'right' and optional \
|
|
208
|
+
'id', or a list of those dicts
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
ResponseException
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
string
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
# If we have an internal key
|
|
218
|
+
if 'meta' in req and 'Authorize-Internal' in req.meta:
|
|
219
|
+
|
|
220
|
+
# Run the internal check
|
|
221
|
+
internal(req)
|
|
222
|
+
|
|
223
|
+
# Return that the request passed the internal check
|
|
224
|
+
return SYSTEM_USER_ID
|
|
225
|
+
|
|
226
|
+
# Else
|
|
227
|
+
else:
|
|
228
|
+
|
|
229
|
+
# Make sure the user has the proper permission to do this
|
|
230
|
+
verify(req.session, permission)
|
|
231
|
+
|
|
232
|
+
# Return that the request passed the verify check
|
|
233
|
+
return req.session.user._id
|
|
234
|
+
|
|
235
|
+
def verify(
|
|
236
|
+
session: _Memory,
|
|
237
|
+
permission: dict | List[dict],
|
|
238
|
+
_return: bool = False
|
|
239
|
+
) -> True:
|
|
240
|
+
"""Verify
|
|
241
|
+
|
|
242
|
+
Checks's if the currently signed in user has the requested right on the
|
|
243
|
+
given permission. If the user has rights, True is returned, else an
|
|
244
|
+
exception of ResponseException is raised
|
|
245
|
+
|
|
246
|
+
Arguments:
|
|
247
|
+
session (memory._Memory): The current session
|
|
248
|
+
permission (dict | dict[]): A dict with 'name', 'right' and optional \
|
|
249
|
+
'id', or a list of those dicts
|
|
250
|
+
_return (bool): Optional, set to True to return instead of raising
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ResponseException
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
bool
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
# Check with the authorization service
|
|
260
|
+
oResponse = body.read('brain', 'verify', {
|
|
261
|
+
'data': permission,
|
|
262
|
+
'session': session
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
# If the response failed
|
|
266
|
+
if oResponse.error:
|
|
267
|
+
raise body.ResponseException(oResponse)
|
|
268
|
+
|
|
269
|
+
# If the check failed, raise an exception
|
|
270
|
+
if not oResponse.data:
|
|
271
|
+
if _return: return False
|
|
272
|
+
raise body.ResponseException(error = body.errors.RIGHTS)
|
|
273
|
+
|
|
274
|
+
# Return OK
|
|
275
|
+
return True
|
brain/helpers/users.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
""" Users
|
|
3
|
+
|
|
4
|
+
Shared methods for accessing user info
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__author__ = "Chris Nasr"
|
|
8
|
+
__copyright__ = "Ouroboros Coding Inc"
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__email__ = "chris@ouroboroscoding.com"
|
|
11
|
+
__created__ = "2022-08-29"
|
|
12
|
+
|
|
13
|
+
# Limit exports
|
|
14
|
+
__all__ = [ 'details', 'EMPTY_PASS', 'exists', 'permissions', 'SYSTEM_USER_ID' ]
|
|
15
|
+
|
|
16
|
+
# Ouroboros modules
|
|
17
|
+
from body import read, ResponseException
|
|
18
|
+
import undefined
|
|
19
|
+
|
|
20
|
+
# Python imports
|
|
21
|
+
from typing import List, Literal
|
|
22
|
+
|
|
23
|
+
# Pip imports
|
|
24
|
+
from brain.helpers.access import generate_key, SYSTEM_USER_ID
|
|
25
|
+
|
|
26
|
+
EMPTY_PASS = '000000000000000000000000000000000000' \
|
|
27
|
+
'000000000000000000000000000000000000'
|
|
28
|
+
"""Default password value"""
|
|
29
|
+
|
|
30
|
+
def details(
|
|
31
|
+
_id: str | List[str],
|
|
32
|
+
fields: List[str] = undefined,
|
|
33
|
+
order: List[str] = undefined,
|
|
34
|
+
as_dict: Literal[False] | str = '_id'
|
|
35
|
+
) -> dict | list:
|
|
36
|
+
"""Details
|
|
37
|
+
|
|
38
|
+
Fetches user info from IDs and returns a single object if a single ID is \
|
|
39
|
+
passed, else returns a dict with each key representing the ID, with the \
|
|
40
|
+
value being the rest of the user details requested via fields. Setting \
|
|
41
|
+
`as_dict` to False, allows for returning a normal list which will be \
|
|
42
|
+
sorted by `order`
|
|
43
|
+
|
|
44
|
+
Arguments:
|
|
45
|
+
_id (str|str[]) The ID(s) to fetch info for
|
|
46
|
+
fields (str[]): The list of fields to return
|
|
47
|
+
order (str[]): The list of fields to order by
|
|
48
|
+
as_dict (False | str): Optional, if false, returns a list, if set, must
|
|
49
|
+
be a field that's passed
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
dict | list
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Init the data by adding the ID(s)
|
|
56
|
+
dData = { '_id': _id }
|
|
57
|
+
|
|
58
|
+
# If we want specific fields
|
|
59
|
+
if fields:
|
|
60
|
+
dData['fields'] = fields
|
|
61
|
+
|
|
62
|
+
# If we want a specific order
|
|
63
|
+
if order:
|
|
64
|
+
dData['order'] = order
|
|
65
|
+
|
|
66
|
+
# Make the read using an internal key
|
|
67
|
+
oResponse = read('brain', 'users/by/id', generate_key({
|
|
68
|
+
'data': dData
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
# If there's an error
|
|
72
|
+
if oResponse.error:
|
|
73
|
+
|
|
74
|
+
# Raise it
|
|
75
|
+
raise ResponseException(oResponse)
|
|
76
|
+
|
|
77
|
+
# If we got a single dictionary, or want the original unaltered list
|
|
78
|
+
if not as_dict or isinstance(oResponse.data, dict):
|
|
79
|
+
return oResponse.data
|
|
80
|
+
|
|
81
|
+
# Convert the data into a dictionary
|
|
82
|
+
dUsers = {}
|
|
83
|
+
for d in oResponse.data:
|
|
84
|
+
|
|
85
|
+
# Pop off the field used as a key
|
|
86
|
+
sKey = d.pop(as_dict)
|
|
87
|
+
|
|
88
|
+
# Store the rest by the key
|
|
89
|
+
dUsers[sKey] = d
|
|
90
|
+
|
|
91
|
+
# Return the users
|
|
92
|
+
return dUsers
|
|
93
|
+
|
|
94
|
+
def exists(
|
|
95
|
+
_id: str | List[str]
|
|
96
|
+
) -> bool:
|
|
97
|
+
"""Exists
|
|
98
|
+
|
|
99
|
+
Returns true if all User IDs passed exist in the system
|
|
100
|
+
|
|
101
|
+
Arguments:
|
|
102
|
+
_id (str | str[]): One or more IDs to check
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# Init the data by adding the ID(s)
|
|
109
|
+
dData = { '_id': _id, 'fields': ['_id'] }
|
|
110
|
+
|
|
111
|
+
# Make the read using an internal key
|
|
112
|
+
oResponse = read('brain', 'users/by/id', generate_key({
|
|
113
|
+
'data': dData
|
|
114
|
+
}))
|
|
115
|
+
|
|
116
|
+
# If there's an error
|
|
117
|
+
if oResponse.error:
|
|
118
|
+
|
|
119
|
+
# Throw it
|
|
120
|
+
raise ResponseException(oResponse)
|
|
121
|
+
|
|
122
|
+
# If we got a string
|
|
123
|
+
if isinstance(_id, str):
|
|
124
|
+
|
|
125
|
+
# Set the return based on whether we got anything or not
|
|
126
|
+
bRet = oResponse.data and True or False
|
|
127
|
+
|
|
128
|
+
# Else, we got a list
|
|
129
|
+
else:
|
|
130
|
+
|
|
131
|
+
# Set the return based on if the counts match
|
|
132
|
+
bRet = len(_id) == len(oResponse.data)
|
|
133
|
+
|
|
134
|
+
# Return
|
|
135
|
+
return bRet
|
|
136
|
+
|
|
137
|
+
def permissions(
|
|
138
|
+
user: str,
|
|
139
|
+
portal: str = undefined
|
|
140
|
+
) -> dict:
|
|
141
|
+
"""Permissions
|
|
142
|
+
|
|
143
|
+
Returns the list of all permissions for the user by portal, or only the
|
|
144
|
+
permissions for one specific portal
|
|
145
|
+
|
|
146
|
+
Arguments:
|
|
147
|
+
user (str): The ID of the user to fetch permissions for
|
|
148
|
+
portal (str): Optional, the specific set of permissions to return
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
dict | None
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
# Init the data by adding the ID
|
|
155
|
+
dData = { 'user': user }
|
|
156
|
+
|
|
157
|
+
# If we have a portal
|
|
158
|
+
if portal is not undefined:
|
|
159
|
+
dData['portal'] = portal
|
|
160
|
+
|
|
161
|
+
# Make the read using an internal key
|
|
162
|
+
oResponse = read('brain', 'permissions', generate_key({
|
|
163
|
+
'data': dData
|
|
164
|
+
}))
|
|
165
|
+
|
|
166
|
+
# If there's an error, raise it
|
|
167
|
+
if oResponse.error:
|
|
168
|
+
raise ResponseException(oResponse)
|
|
169
|
+
|
|
170
|
+
# Return the permissions
|
|
171
|
+
return oResponse.data
|