Compare commits
No commits in common. "7d309491fc545dae4f6c46e8b85bfeed7a754cb4" and "e0a53dce68451944f2aee3cb433b870bc8d086ee" have entirely different histories.
7d309491fc
...
e0a53dce68
14 changed files with 26 additions and 68 deletions
|
|
@ -29,7 +29,7 @@ zope = [
|
|||
"zope.traversing",
|
||||
]
|
||||
api = ["fastapi[standard]"]
|
||||
auth = ["authlib", "pyjwt[crypto]", "cryptography", "pwdlib[argon2]", "requests"]
|
||||
auth = ["authlib", "pyjwt[crypto]", "cryptography", "requests"]
|
||||
zopetest = ["zope.testrunner"]
|
||||
apitest = ["pytest", "httpx2"]
|
||||
|
||||
|
|
@ -42,6 +42,3 @@ testpaths = ["tests"]
|
|||
#python_files = ["test_api.py"]
|
||||
# use .pytest.ini file for local test config
|
||||
|
||||
[tool.mypy]
|
||||
follow_imports = 'skip'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
# scopes.api.auth user
|
||||
|
||||
from fastapi import APIRouter
|
||||
import jwt
|
||||
from pwdlib import PasswordHash
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List
|
||||
|
||||
|
|
@ -15,25 +13,9 @@ class User(BaseModel):
|
|||
login: str
|
||||
display_name: str
|
||||
email: str | None = None
|
||||
pw_hash: str | None = Field(exclude=True, default=None)
|
||||
pw_hash: str | None = None
|
||||
groups: List[str] = Field(default_factory=list)
|
||||
|
||||
# password handling, authentication
|
||||
|
||||
pw_hash = PasswordHash.recommended()
|
||||
|
||||
def hashPassword(pw):
|
||||
return pw_hash.hash(pw)
|
||||
|
||||
def verifyPassword(plain, hashed):
|
||||
return pw_hash.verify(plain, hashed)
|
||||
|
||||
async def authenticate(storage: deps.Storage, token: deps.Auth):
|
||||
payload = jwt.decode(token, settings.jwt_key, algorithms=[settings.jwt_algorithm])
|
||||
userName = payload.get('sub')
|
||||
user = getUser(storage, userName)
|
||||
return user
|
||||
|
||||
# routes
|
||||
|
||||
router = APIRouter()
|
||||
|
|
|
|||
|
|
@ -9,12 +9,8 @@ from scopes import config
|
|||
from scopes.storage import common
|
||||
|
||||
type Storage = Annotated[common.Storage, Depends(storage)]
|
||||
type Auth = Annotated[str, Depends(authenticator)]
|
||||
|
||||
def storage():
|
||||
stf = config.StorageFactory(config.settings)
|
||||
return stf(config.settings.dbschema)
|
||||
|
||||
def authenticator():
|
||||
return config.Authenticator()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ if '' not in sys.path:
|
|||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
import transaction # type: ignore
|
||||
import transaction
|
||||
|
||||
from scopes.api import tracking
|
||||
from scopes.api.auth import user
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# scopes.config.py
|
||||
# py-scopes/apidemo/config.py
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from scopes.storage.db.postgres import StorageFactory
|
||||
|
||||
|
|
|
|||
|
|
@ -55,9 +55,3 @@ class Record:
|
|||
def toObject(self, cls):
|
||||
return cls(**self.asDict(flatten=True))
|
||||
|
||||
@classmethod
|
||||
def fromObject(cls, obj):
|
||||
inp = obj.model_dump()
|
||||
headValues = [inp.pop(hf) for hf in cls.headFields]
|
||||
return cls(*headValues, data=inp)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
"""Core classes for concept map structure."""
|
||||
|
||||
from zope.interface import implementer # type: ignore
|
||||
from zope.interface import implementer
|
||||
from scopes.interfaces import IContainer
|
||||
from scopes.storage.common import registerContainerClass, registry
|
||||
from scopes.storage import tracking
|
||||
from scopes.storage.tracking import Container, Track
|
||||
|
||||
defaultPredicate = 'standard'
|
||||
|
||||
|
||||
class Concept(tracking.Track):
|
||||
class Concept(Track):
|
||||
|
||||
headFields: tuple[str, ...] = ('name',)
|
||||
headFields = ['name']
|
||||
|
||||
def parents(self, predicate=None):
|
||||
return (r.getFirst() for r in self.parentRels(predicate))
|
||||
|
|
@ -34,17 +34,17 @@ class Concept(tracking.Track):
|
|||
rels.save(Triple(self.uid, child.uid, predicate))
|
||||
|
||||
|
||||
class Concepts(tracking.Container):
|
||||
class Concepts(Container):
|
||||
|
||||
insertOnChange = False
|
||||
indexes: tracking.Indexes = None
|
||||
indexes = None
|
||||
|
||||
def queryRels(self, **crit):
|
||||
#pred = crit.get(predicate)
|
||||
#if pred is not None and isinstance(pred, ('string', 'bytes')):
|
||||
# crit['predicate'] = self.storage.getContainer(Predicate).queryLast(name=pred)
|
||||
for k, v in crit.items():
|
||||
if isinstance(v, tracking.Track):
|
||||
if isinstance(v, Track):
|
||||
crit[k] = v.uid
|
||||
rels = self.storage.getContainer(Triple)
|
||||
return rels.query(**crit)
|
||||
|
|
@ -69,9 +69,9 @@ def storePredicate(storage, name):
|
|||
preds.save(Predicate(name))
|
||||
|
||||
|
||||
class Triple(tracking.Track):
|
||||
class Triple(Track):
|
||||
|
||||
headFields = ('first', 'second', 'predicate')
|
||||
headFields = ['first', 'second', 'predicate']
|
||||
prefix = 'rel'
|
||||
|
||||
def getFirst(self):
|
||||
|
|
@ -85,7 +85,7 @@ class Triple(tracking.Track):
|
|||
|
||||
|
||||
@registerContainerClass
|
||||
class Rels(tracking.Container):
|
||||
class Rels(Container):
|
||||
|
||||
itemFactory = Triple
|
||||
indexes = [('first', 'predicate', 'second'),
|
||||
|
|
@ -99,7 +99,7 @@ class Rels(tracking.Container):
|
|||
@implementer(IContainer)
|
||||
class Type(Concept):
|
||||
|
||||
headFields = ('name', 'tprefix')
|
||||
headFields = ['name', 'tprefix']
|
||||
prefix = 'type'
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from sqlalchemy import create_engine
|
|||
from sqlalchemy import BigInteger
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
import transaction # type: ignore
|
||||
from zope.sqlalchemy import register, mark_changed # type: ignore
|
||||
import transaction
|
||||
from zope.sqlalchemy import register, mark_changed
|
||||
|
||||
from scopes.storage.common import StorageFactory as BaseStorageFactory
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# scopes.storage.folder
|
||||
|
||||
from zope.interface import implementer # type: ignore
|
||||
from zope.interface import implementer
|
||||
|
||||
from scopes.interfaces import IContainer, IReference
|
||||
from scopes.storage.common import registerContainerClass
|
||||
|
|
@ -22,7 +22,7 @@ class DummyFolder(dict):
|
|||
@implementer(IContainer, IReference)
|
||||
class Folder(Track):
|
||||
|
||||
headFields = ('parent', 'name', 'ref')
|
||||
headFields = ['parent', 'name', 'ref']
|
||||
prefix = 'fldr'
|
||||
|
||||
def values(self):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ in order to store attribute values in a SQL database.
|
|||
This is currently in concept and exploration state.
|
||||
"""
|
||||
|
||||
import transaction # type: ignore
|
||||
import transaction
|
||||
|
||||
_not_found = object()
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from sqlalchemy import and_
|
|||
from scopes import shape
|
||||
from scopes.storage.common import registerContainerClass
|
||||
|
||||
type Indexes = list[tuple[str, ...]] | None
|
||||
|
||||
class Record(shape.Record):
|
||||
|
||||
|
|
@ -57,7 +56,7 @@ class Record(shape.Record):
|
|||
|
||||
class Track(Record):
|
||||
|
||||
headFields: tuple[str, ...] = ('taskId', 'userName')
|
||||
headFields = ('taskId', 'userName')
|
||||
prefix = 'rec'
|
||||
|
||||
|
||||
|
|
@ -67,8 +66,8 @@ class Container:
|
|||
itemFactory = Track
|
||||
tableName = 'tracks'
|
||||
insertOnChange = True # always insert new track when data are changed
|
||||
indexes: Indexes = None # is set in registerContainerClass()
|
||||
#indexes = [('username',), ('taskid', 'username')] # explicit setting in subclass
|
||||
indexes = None # default, will be overwritten by registerContainerClass()
|
||||
#indexes = [('username',), ('taskid', 'username')] # or put explicitly in class
|
||||
|
||||
table = None
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class User(concept.Concept):
|
|||
prefix = 'user'
|
||||
|
||||
|
||||
|
||||
@registerContainerClass
|
||||
class Users(concept.Concepts):
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,7 @@ DBUSER=testuser
|
|||
DBPASSWORD=secret
|
||||
DBSCHEMA=testing
|
||||
|
||||
AUTHENTICATOR=testing
|
||||
|
||||
# openssl rand -hex 32:
|
||||
JWT_KEY=391a521ee5ba2b6ce67b8fdbb9f0ca88f40a87d3bc1e82974381b647bb0fc776
|
||||
JWT_ALGORITHM=HS256
|
||||
|
||||
OIDC_PROVIDER=http://oidc.test
|
||||
OIDC_PROVIDER=https://oidc.example.com
|
||||
OIDC_CLIENT_ID=12345
|
||||
OIDC_COOKIE_CRYPT=secret_crypt
|
||||
OIDC_ORGANIZATION_ID=12346
|
||||
|
|
|
|||
|
|
@ -61,15 +61,10 @@ def test_tracking(storage, caplog):
|
|||
|
||||
def test_user(storage, caplog):
|
||||
users = storage.create(st_user.Users)
|
||||
pw = 'secret'
|
||||
pw_hash = api_user.hashPassword(pw)
|
||||
u01 = st_user.User('jim', 'jim', data=dict(display_name='Jim', pw_hash=pw_hash))
|
||||
u01 = st_user.User('jim', 'jim', data=dict(display_name='Jim'))
|
||||
u01id = users.save(u01)
|
||||
storage.commit()
|
||||
with TestClient(app) as clt:
|
||||
caplog.set_level(config.settings.log_level)
|
||||
users = clt.get('/users').json()
|
||||
assert len(users) == 1
|
||||
u01i = users[0]
|
||||
assert u01i.get(pw_hash) is None
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue