atviriduomenys / katalogas

Lietuvos atvirų duomenų katalogas (data.gov.lt).
14 stars 2 forks source link

Saugyklos kliento valdymas iš katalogo #261

Closed sirex closed 8 months ago

sirex commented 1 year ago

Kai katalogo organizacijos atstovui sugeneruojamas prieigos raktas, toks pat raktas turėtu būti sukuriamas ir Saugykloje, naudojant Saugyklos klientų valdymo API (https://github.com/atviriduomenys/spinta/issues/122).

Kiekvienas klientas turi turėti prieigą tik prie tokių rinkinių, kuriuos turi teisę administruoti.

Prieigą prie Saugyklos gali suteikti tik koordinatorius.

Epic

Priklauso nuo

adp-atea commented 11 months ago

O kad kataloge sugeneruot rakta, tam tiktu koks nors mygtukas? Jeigu taip, tai kokioje formoje jis turetu buti? Tarkim tiktu user profile?

sirex commented 10 months ago

Integracija su Saugyklos API

Visi raktai saugomi Saugykloje, per Auth clients API. API naudojimo pavyzdžius galima pamatyti čia:

https://github.com/atviriduomenys/spinta/blob/master/notes/access/public.sh

Papildomai Saugyklos raktai saugomi ir Katalogo pusėje, lentelėje ApiKey, kurioje atsiranda nauji laukai:

Atsiranda nauja lentelė ApiScope su tokiais laukais:

scope formatas aprašytas čia:

https://spinta.readthedocs.io/en/latest/manual/access.html#scopes

ApiKey sinchronizavimas

Kiekvieną kartą atidarius raktų puslapį, daroma API užklausa:

https://github.com/atviriduomenys/spinta/blob/ea34b27adab8f7e964e2b240b383c3c0ae38879c/notes/access/public.sh#L296

Ir atnaujinama ApiKey lentelė įtraukiant naujus raktus, kurie yra Saugykloje, bet kurių nėra Kataloge. ApiKey.organization nustatomas, pagal Saugyklos client_name, kuris turėtu sutapti su Organization.name. Jei nesutampa, tada ApiKey.organization paliekamas NULL, tačiau išsaugomas ApiKey.client_id ir ApiKey.client_name.

Jei Saugykloje nebėra tokio client_id, o ApiKey lentelėje yra įrašas su tokiu client_id, tada tas įrašas ApiKey lentelėje pažymimas, kaip enabled = false.

Jei Saugyklos API yra nepasiekiamas, tada sąrašo viršuje rodoma raudona juosta su informacija, kad nepavyko susisiekti su Saugyklos API, todėl raktai rodomi lentelėje gali nesutapti su raktais Saugykloje.

ApiScope sinchronizavimas

Kiekvieną kartą atidarius rakto formą, daroma API užklausa:

https://github.com/atviriduomenys/spinta/blob/ea34b27adab8f7e964e2b240b383c3c0ae38879c/notes/access/public.sh#L350

Ir atnaujinama ApiScope lentelė įtraukiant scope sąrašą į lentelę.

Pagal scope pavadinimą nustatomas ApiSope.organization ir ApiScope.dataset.

Tarkime, turint scope spinta_datasets_gov_ivpk_getall, nustatoma ivpk pavadinimas, kuris sutampa su Organization.name. Jei organizacijos nustatyti nepavyko, tada ApiScope.organization nustatom į null.

Tarkime, turint scope spinta_datasets_gov_ivpk_adp_catalog_getall nustatomas Dataset.name pavadinimas datasets/gov/ivpk/adp/catalog, pagal kurį išsaugom ApiScope.dataset, ne pavadinimo nustatyti nepavyko, tada saugom null.

Reikia atkreipti dėmesį, kad Dataset.name gali būti datasets/gov/ivpk/adp_catalog, kurio scope bus spinta_datasets_gov_ivpk_adp_catalog, todėl reikia, kad veiktų atpažinimas tiek adp_catalog, tiek adp/catalog atvejais.

Jei Saugykloje nėra tam tikro scope, o ApiScope jis yra, tada įrašas iš ApiScope turi būti pašalinamas.

Gali būti ir tokie scope, kaip spinta_set_meta_fields, kurie nėra susiję su jokia organizacija ar duomenų rinkiniu, tokiu atveju, nei ApiKey, nei ApiScope nėra priskiriamas jokiai organizacijai ar rinkiniui.

Jei ApiScope.organization ar ApiScope.dataset jau buvo nurodyti, pakartotinai jų nenustatome. Nustatymą darome tik tuo atveju, jei organization ir dataset yra null.

Jei Saugyklos API yra nepasiekiamas, tada sąrašo viršuje rodoma raudona juosta su informacija, kad nepavyko susisiekti su Saugyklos API, todėl raktai rodomi lentelėje gali nesutapti su raktais Saugykloje.

Administravimas

Kadangi client_name iš Saugyklos nebūtinai sutaps su Organization.name, tai tam tikrais atvejais, reikės daryti pataisymus rankiniu būdu. Todėl ApiKey turėtu būti pasiekiamas per Django admin aplinką.

ApiScope turėtu taip pat būti pasiekiamas per ApiKey, kaip InlineModelAdmin.

ApiKey administravime, reikia tokių filtrų:

Saugumas

Sinchronizuojant raktus iš Saugyklos, svarbu užtikrinti, kad raiktai ir scope būtų priskiriami teisingoms organizacijoms ir duomenų rinkiniams.

Kad tai veiktu, reikia patikrinti ar Organization.name ir Dataset.name turi unique constraint, kad neatsitiktų taip, kad vienas raktas, priskirtas kelioms organizacijoms ar keliems rinkiniams.

Organizacijos raktų puslapis

image -- https://data.gov.lt/orgs/269/

Organizacijos kontekste, atsiranda naujas puslapis „Raktai“.

Raktų puslapyje rodomos dvi lentelės su tokiais stulpeliais

Vidiniai raktai

Vidiniai raktai yra raktai, kurios sukūrė ir valdo pati organizacija.

Paspaudus mygtuką [Keisti], atidaroma vidinio rakto forma.

Paspaudus mygtuką [Pašalinti], atidaromas patvirtinimo puslapis, kuriame klausiama, ar tikrai norima pašalinti. Sutikus, daroma

https://github.com/atviriduomenys/spinta/blob/ea34b27adab8f7e964e2b240b383c3c0ae38879c/notes/access/public.sh#L383

Užklausa ir raktas pašalinamas iš serverio.

Išoriniai raktai

Kitų organizacijų raktai, kurie turi prieigą, prie kontekstinės organizacijos duomenų. Nustatomi pagal ApiScope.organization, jei bent vienas sutampa su kontekstine organizacija, tada raktas rodomas išorinių raktų sąraše.

Rakto forma

Organizacijos raktų puslapyje rodomas mygtukas [Naujas raktas], kurį paspaudus rodoma forma su tokiais laukais:

Išsaugojus formą, jei kuriamas naujas raktas, automatiškai priskiriami tokie scope:

Jei saugoma esamo rakto forma, tada scope neliečiami.

Sukūrus naują raktą, generuojamas ilgas saugus slaptažodis, kuris išsaugojus formą yra parodomas vieną kartą virš leidimų lentelės, su informacija, kad slaptažodis bus rodomas tik vieną kartą.

Rakto peržiūra

Rakto peržiūroje rodome:

Vidinio rakto atveju, kuri kontekstinė organizacija ir ApiKey.organization sutampa, rodome visus scope.

Išorinio rakto atveju, kur kontekstinė organizacija ir ApiKey.organization nesutampa, rodome tik tuos raktus, kurių ApiScope.organization sutampa su kontekstine organizacija.

Scope lentelėje rodome tokius stulpelius:

Paspaudus [Keisti] atidaroma Leidimo forma.

Paspaudus [Pašalinti] atidaromas patvirtinimo puslapis ir sutikus su patvirtinimu, pašalinami visi konkretaus dataset ar organizacijos scope.

Leidimų lentelė yra grupuojama pagal objektą. Tarkime, jei turime tokius scope:

Tada lentelė atrodys taip:

Objektas Skaityti Rašyti Valyti
set_meta_fields [Išjungti] [Trinti]
(viskas) [Išjungti] [Keisti] [Trinti]
Informacinės visuomenės plėtros komitetas [Išjungti] [Keisti] [Trinti]
COVID-19 pandemijos duomenys [Išjungti] [Keisti] [Trinti]

Lentelės viršuje rodome mygtuką [Naujas leidimas], kurį paspaudus atidaroma Naujo leidimo forma.

Rakto slaptažodžio keitimas

Rakto peržiūros puslapyje rodyti mygtuką [Keisti slaptažodį], kurį paspaudus atidaroma forma, su sugeneruotu nauju slaptažodžiu, tačiau naujas slaptažodis kol kas niekur nesaugomas.

Tik paspaudus mygtuką [Keisti slaptažodį], realiai pakeičiamas slaptažodis tiek Saugykloje, tiek ApiKey lentoje api_key lauke, užšifruotoje formoje.

Keitimo formoje, turi būti rodomas pranešimas, kad raktas bus parodytas tik vieną kartą, po pakeitimo, nebebus galimybės pamatyti rakto, todėl jis turi būti išsisaugotas.

Leidimo forma

Leidimo formoje rodome tokius laukus:

Reikia patikrinti ar įvestas scope yra Organization.name, Dataset.name arba vienas iš custom scope, kuris yra tik vienas: set_meta_fields. Jei nei vienas variantas neatitinka, tada mesti klaidą.

Jei pasirinktas custom scope (set_meta_fields) ir pažymėtas bent vienas checkbox, mesti klaidą, nes custom scopes neturi action.

Saugant formą, reikia sukurti eilę ApiScope įrašų, kiekvienam pažymėtam action atskirai.

Jei nurodyta kitos organizacijos scope, tada ApiScope turi būti nustatytas į enabled = false ir į Saugyklą tokie scope neturi būti siunčiami.

sirex commented 10 months ago

Jei viena organizacija (Prašytojas), prašo prieigos prie kitos organizacijos duomenų (Savininkas), tada Savininkui turi būti išsiųsta užduotis ir el. laiškas, apie prašymą prieiti prie duomenų. Užduotyje turi būti nuoroda į Prašytojo rakto peržiūrą, Savininko organizacijos kontekste, kur Savininkas gali įjungti prašomus leidimus prie duomenų.

sirex commented 9 months ago

See pull request comments: https://github.com/atviriduomenys/katalogas/pull/822

sirex commented 9 months ago

Dėl Saugyklos kliento, siūlau naudoti tokius nustatymus:

Vietoj dabar naudojamų:

CLIENTS_API_URL = env('CLIENTS_API_URL', default='https://get-test.data.gov.lt/auth/clients/')
CLIENTS_AUTH_BEARER = env('CLIENTS_AUTH_BEARER', default='')

Kadangi prieiga bus reikalinga keliose skirtingose vietose. Todėl tam reikalui siūlau sukurti atskirą funkciją, kuri sukuria requests.Session ir autorizuoja klientą.

Autorizacijai, access token reikia cache'uoti, patikrinant tokeno expiration date. Likus kokiai valandai iki tokeno galiojimo pabaigos, paimamas naujas tokenas, naudojant client secret.

Dėl kešavimo žiūrėti: https://docs.djangoproject.com/en/3.2/topics/cache/#the-low-level-cache-api

Ir labai svarbu, negalima saugoti jokių secretų kodo repozitorijoje!

sirex commented 9 months ago

https://github.com/atviriduomenys/katalogas/issues/262#issuecomment-1859242174

sirex commented 9 months ago

Klaida vis dar nepataisyta, žiūrėti https://github.com/atviriduomenys/katalogas/issues/262#issuecomment-1859242174.

Po atnaujinimo, gaunu lygiai tokią pačią klaidą.

sirex commented 8 months ago

Atidarius: https://test.data.gov.lt/orgs/269/apikeys/

Gaunu tokią klaidą:

Traceback (most recent call last):
  File "vitrina/orgs/views.py", line 1050, in get_context_data
    ApiKey.objects.create(
  File "django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "django/db/models/query.py", line 451, in create
    obj = self.model(**kwargs)
  File "django/db/models/base.py", line 485, in __init__
    _setattr(self, field.name, rel_obj)
  File "django/db/models/fields/related_descriptors.py", line 215, in __set__
    raise ValueError(
ValueError: Cannot assign "<MP_NodeQuerySet [<Organization: Valstybės duomenų agentūra>]>": "ApiKey.organization" must be a "Organization" instance.

Matau, kad ApiKey.organization bandomas priskirit QuerySet objektas, vietoj Organization:

https://github.com/atviriduomenys/katalogas/blob/dd063a4c047c4a4380c6af8f3d03c4fda9597e9c/vitrina/orgs/views.py#L1044-L1055

sirex commented 8 months ago

https://github.com/atviriduomenys/katalogas/commit/eef23c0197f15f9795d28f6bfd268b8d03a4e2ca commit metu buvo pridėtas ApiKey.enabled laukas, kuris neturi default re

Atidarius organizacijos raktų puslapį, pavyzdžiui https://data.gov.lt/orgs/83/apikeys/ visi ApiKey.enabled pakeičiami į False, dėl to nustoja veikti absoliučiai visi, visų organizacijų raktai.

Dėl to yra sugadinti visi raktų duomenys, kadangi neaišku, kurie raktai buvo aktyvūs, o kurie ne. Reikės galvoti, kaip atstatyti sugadintus duomenis.

Kol kas prašau pataisyti, kad nebūtų išjungiami raktai.

Problema yra šioje vietoje:

https://github.com/atviriduomenys/katalogas/blob/80e32ce3ebb990e6d0326d18ac1e9bac324a8e4f/vitrina/orgs/views.py#L1058-L1062

sirex commented 8 months ago

Sinchronizuojant Saugyklos raktus į ApiKey lentelę, reikia atkreipti dėmesį, kad ApiKey lentelė yra naudojama tiek Saugyklos, tiek Katalogo raktams.

Sinchronizuojant Saugyklos raktus, nereikia liesti Katalogo raktų.

Saugyklos raktus galima atpažinti pagal ApiKey.client_id, kuris ateina iš Saugyklos. Katalogo atveju ApiKey.client_id bus NULL.

Iš Saugyklos gauname tik client_id ir client_name, jei jie sutampa, tada siūlau ApiKey lentelėje nieko nekeisti. Jei ApiKey lentelėje yra client_id, o Saugykloje tokio nėra, tada tik tokiu atveju daryti ApiKey.enable = False, taip mažinant rašymo veiksmų kiekį.

ApiKey sinchronizavimui, užtenka tik vienos /auth/clients užklausos.

ApiScope lentelę siūlau sinchronizuoti, tik vienai organizacijai, kurios raktų sąrašas atidarytas, kitų organizacijų raktų sinchronizuoti nereikia. Aktyvios organizacijos raktai nustatomi pagal ApiKey.organization ir ApiScope.organization.

Sinchronizuojant, reikia stengtis mažinti rašymo veiksmus į duomenų bazę ir užklausų skaičių į Saugyklą, atliekant tiek rašymo veiksmų ir HTTP užklausų, kiek yra būtina tos organizacijos raktų sinchronizavimui.