Koncepti
Kako deluje wkidentity: kaj paket upravlja, od česa je odvisna zaščita in zakaj so postopki zasnovani tako, kot so. Kaj želite narediti, piše na strani Identity; kaj želite poiskati, v Tehnični referenci.
Definicija
wkidentity skrbi za identiteto računa ves čas njegovega obstoja. Paket združuje dve področji, ker odgovarjata na isto vprašanje – kdo je to:
- Drugi dejavnik. TOTP po RFC 6238 (združljiv z Microsoft Authenticator, Google Authenticator in drugimi standardnimi aplikacijami), ki si ga uporabnik sam nastavi na strani profila (
do=profile), po izbiri tudi enkratna koda po e-pošti; poleg tega obveznost za posamezen imenski prostor in neobvezno preverjanje ob prijavi. Dopolnjuje ju notranja preizkusna stran, ki poljubno skrivnost Base32 preveri z lastnim izračunom TOTP, ne da bi skrivnost zapustila brskalnik. - Postopki z enkratno kodo. Ponastavitev gesla, samoregistracija in povabilo, ki ga pošlje skrbnik – zgrajeni po načelu, da gesla nikoli ne pozna nihče razen imetnika računa. Glejte Postopki z enkratno kodo.
Paket ni authtype (zaledje, v katerem DokuWiki hrani račune in preverja gesla): upravljanja
uporabnikov ne zamenja, temveč deluje pred njim in ob njem.
Zagotovilo je lastnost seje, ne računa
isEnrolled() po nastavitvi dejavnika za vedno odgovarja pritrdilno – to je lastnost računa brez
časovne meje. currentAssurance() odgovarja na drugo vprašanje: kaj je ta seja zdaj dokazala.
Račun, ki ima dejavnik nastavljen že leta, ima lahko sejo, ki ni nikoli opravila dodatnega
preverjanja (step-up).
enforceNamespacePolicy() do 8. avgusta 2026 zamenjevala – to je bil
dejanski povod za utrditev varnosti. Kljuka ACL je preverjala samo isEnrolled($user); pri
izklopljenem enforce_login (privzeto) je zato enkrat nastavljen dejavnik zadoščal za vedno, ne
glede na to, ali ga je trenutna seja kdaj dokazala. enforceNamespacePolicy() zdaj veljavno
zahtevo razreši prek PolicyStore::effectiveRequirement() (glejte »Pravilnik« spodaj) in jo
preveri proti dejanskemu zagotovilu seje (helper_plugin_wkidentity_contract::satisfies());
isEnrolled() odloča le še o tem, ali bi bila zahteva sploh izpolnljiva – račun brez vpisanega
dejavnika je še naprej zavrnjen takoj in nikoli poslan v dodatno preverjanje, ki ga ne more opraviti.
Zavrnitev zdaj navede enega od dveh razlogov: »ni vpisan« (povezava na vpis) ali »vpisan, a ta seja
tega ni dokazala« (povezava na pravo dodatno preverjanje, ki se vrne na prvotno stran,
do=wkidentity_stepup_start). Podrobnosti: docs/threat-model.md, ugotovitev F6.
| Stopnja | Pomen |
|---|---|
NONE | ni seje ali anonimni obisk |
AUTHENTICATED | samo prijava v jedru, v tej seji brez drugega dejavnika |
TOTP | drugi dejavnik dokazan z aplikacijo za preverjanje |
MAIL_OTP | drugi dejavnik dokazan s kodo, poslano po e-pošti |
TOTP in MAIL_OTP sta alternativi, ne stopnici na lestvi – oboje sprejme samo zahteva
MFA_ANY. Od utrditve varnosti ima lahko račun oba dejavnika hkrati (vsakega je mogoče
nastaviti in odstraniti neodvisno od drugega); posamezna seja pa vedno dokaže samo enega od njiju.
Dokazana stopnja zdaj poteče. Nastavitev assurance_ttl (privzeto 3600 sekund) določa, kako
dolgo opravljeno dodatno preverjanje – ali dejavnik, dokazan ob prijavi – šteje kot zagotovilo te
seje, preden se seja vrne na AUTHENTICATED (ne na NONE: prijava ostane, poteče samo dodatni
dokaz dejavnika). Za območje, ki zahteva dejavnik, je treba po izteku znova opraviti dodatno
preverjanje. To nadomešča prejšnjo opredelitev, da »nobena stopnja v seji ne poteče«.
Dodatno preverjanje s preverjenim ciljem vrnitve
Prej ga ni bilo: vsaka preusmeritev v LoginGate (razredu, ki prijavo zadrži do vnosa kode)
in v action.php je brezpogojno kazala na $conf['start'], nikoli nazaj na prvotni cilj. Zdaj
require() po potrebi preusmeri na do=wkidentity_stepup – isto lupino obrazca kot pri
do=wkidentity_verify (ponovno uporabljena verifyCode()/verifyEmailChallenge(), brez nove
kriptografije) – in se po pravilni kodi vrne na preverjeni cilj. Uporabnik tako po potrditvi kode
pristane tam, kamor je bil namenjen, in ne na začetni strani.
cleanID(), ali ID prispevka po pogodbi Contract v1, ki pri wkfluentui dejansko
obstaja. V vseh drugih primerih velja $conf['start'], ista varna privzeta vrednost, ki jo v tem
vtičniku že uporablja vsaka obstoječa preusmeritev. Povezave, ki bi uporabnika po vnosu kode
preusmerila na tuje spletno mesto, zato ni mogoče sestaviti. Ta disciplina namenoma posnema, kako
jedro DokuWikija razreši cilj vrnitve svojega prijavnega obrazca – nikoli z naslovom, ki ga
izbere klicatelj.
Pravilnik: imenski prostor, skupina, uporabnik in dokumentirano razreševanje sporov
Od utrditve varnosti PolicyStore (razred, ki hrani in vrednoti pravilnik) upošteva štiri neodvisne
osi namesto samo imenskega prostora: namespace (primerjava predpone kot v conf/acl.auth.php),
group (vsaka skupina trenutne seje), user (natančno prijavno ime) ter
plugin/module/view/command/adminAction (natančen ID, ki ga določi vtičnik sam).
Za eno zahtevo lahko hkrati velja več osi – PolicyStore::effectiveRequirement($scope, $user, $groups)
vse ustrezne vnose združi prek PolicyStore::combine().
combine() obravnava štiri vrednosti zahteve kot majhno mrežo, ne kot številsko lestvico –
prav zato, ker sta TOTP in MAIL_OTP alternativi, ne stopnici. AUTHENTICATED je nevtralni
element (druge strani ne spremeni); MFA_ANY je pod obema konkretnima dejavnikoma (vsak od njiju
že izpolni »kateri koli MFA«); TOTP in MAIL_OTP sta sicer neprimerljiva. Vsaka definirana
kombinacija da strožjo od obeh vrednosti – nikoli šibkejše od katere koli od njiju. Zato je
trditev »pravilnik lahko vtičniku lastno najnižjo zahtevo samo zviša, nikoli je tiho ne oslabi«
dokazljiva, ne zgolj obljubljena.
Edina nedefinirana celica je pravi spor: vnos za imenski prostor zahteva TOTP, vnos za skupino
ali uporabnika pa MAIL_OTP – dva različna konkretna dejavnika, ki ju nobena posamezna stopnja
currentAssurance() ne more izpolniti hkrati. effectiveRequirement() spor razreši po
specifičnosti: vnose združuje od najsplošnejšega do najbolj specifičnega (imenski prostor oziroma
neposredni obseg, nato vsaka ustrezna skupina, nato uporabnik – in eno raven višje, v
satisfies()/require(), na koncu še izrecna zahteva vtičnika med izvajanjem, kot najbolj
specifičen glas sploh). Sam spor se zabeleži prek Trace::note() – pravilnik, ki si nasprotuje,
zasluži pozornost upravljavca, ne tihega metanja kovanca.
Imenski prostor, ki je naveden samo na starejšem seznamu required_namespaces (seznam je nastal
pred required_scopes in nima lastne stopnje zahteve), se še naprej upošteva, in sicer z zahtevo
MFA_ANY. To je najzvestejše branje tega, kar je seznam vedno pomenil (»zadošča kateri koli vpisan
dejavnik«), le da se zdaj preverja proti dejanskemu zagotovilu seje, ne zgolj proti vpisu računa.
Potek dela (vpis)
Vpis je namenoma postopek v dveh korakih in ne takojšen vklop, da uporabnik dvostopenjskega preverjanja nikoli ne »vklopi« s skrivnostjo, ki je njegova aplikacija za preverjanje v resnici ni nikoli poskenirala:
- Začetek – gumb »Nastavi dvostopenjsko preverjanje« na
do=profile(viden samo, dokler dejavnik ni vpisan). Ustvari novo naključno skrivnost (auth_randombytes(), 160 bitov) in jo shrani samo v sejo – na disk se še nič ne zapiše. - Potrditev – stran prikaže kodo QR, skrivnost za ročni vnos in polje za šestmestno kodo. Šele pravilna koda (preverjena proti čakajoči skrivnosti, s toleranco ±1 časovni korak) skrivnost šifrirano trajno shrani (
saveSecret()) in čakajoči vnos odstrani iz seje. Napačna koda čakajoče skrivnosti ne spremeni – uporabnik lahko isto kodo QR znova poskenira oziroma vnese. - Stanje in izklop – ko je dejavnik vpisan, stran profila pokaže samo še stanje »aktivno« in potrditveno polje »Izklopi dvostopenjsko preverjanje«, ki skupaj z običajnim gumbom profila »Shrani« skrivnost odstrani (
removeSecret()).
doku.php pokliče session_write_close() pred
act_dispatch(). Preprost zapis $_SESSION[…] = … v kljuki ACTION_ACT_PREPROCESS zato
posodobi samo tabelo v pomnilniku trenutne zahteve in nikoli ne pride na disk – naslednja
zahteva (oddaja potrditve) čakajoče skrivnosti ne vidi več in vsaka koda bi bila »napačna«.
Rešitev: vsak zapis čakajoče skrivnosti v sejo se sam znova ovije v
@session_start(); …; session_write_close(); (isti vzorec, ki ga jedro DokuWikija uporablja v
updateprofile() iz auth.php in v send_redirect() iz common.php). Brez tega vzorca
ponovnega odpiranja potrditev odpove ne glede na okolje – simptom se je enako pokazal na MicroApache
in IIS.
Notranja preizkusna stran (do=wkidentity_debug; povezavo nanjo v profilu vklopi nastavitev
show_debug_link) je od pravega vpisa popolnoma ločena: kode TOTP za poljubno prilepljeno skrivnost
izračuna izključno v brskalniku (Web Crypto HMAC-SHA1) in strežniku nikoli ničesar ne pošlje.
Namenjena je preverjanju združljivosti z aplikacijami za preverjanje, ne prikazu pravih uporabniških
skrivnosti.
Potek dela (vpis po e-pošti)
Drugi zavihek v istem razdelku dvostopenjskega preverjanja, viden samo, če je vklopljena nastavitev
allow_email_method (privzeto izklopljena, glejte »Konfiguracija« v Tehnični referenci). Velja
isto načelo dveh korakov kot pri TOTP: »Nastavi po e-pošti« pošlje šestmestno kodo na naslov, ki je
trenutno shranjen v profilu (sendEmailChallenge(); koda je na strežniku shranjena kot zgoščena
vrednost s soljo – od utrditve varnosti password_hash(), prej SHA-256 brez soli – z veljavnostjo
10 minut in nikoli v golem besedilu); šele pravilno vnesena koda metodo vklopi (saveEmailMethod()).
Od utrditve varnosti lahko uporabnik uporablja obe metodi hkrati, TOTP in e-pošto; nastavi in
odstrani ju neodvisno drugo od druge (hasFactor()/enrolledFactors()/removeFactor()).
Posamezna prijava ali dodatno preverjanje pri tem sprejme tisti nastavljeni dejavnik, s katerim se
ujema vnesena koda.
Obveznost ob prijavi
Izvedena je z LoginGate.php in jedrno kljuko AUTH_LOGIN_CHECK (inc/auth.php, pred dokončno
vzpostavitvijo seje). To je edina točka v celotnem ciklu zahteve, na kateri je mogoče dodatni dejavnik
vriniti pred dokončano prijavo, ker jedrna auth_login() sicer preverjanje gesla in vzpostavitev
seje opravi v enem koraku.
Potek: pravilno geslo + vpisan dejavnik + vklopljena nastavitev enforce_login → prijava se
ustavi. Čakajoče stanje (uporabnik, geslo, nastavljene metode, rok, števec neuspelih poskusov) se za
največ login_attempt_window sekund shrani v sejo (vzorec ponovnega odpiranja, glejte zgoraj),
stran do=wkidentity_verify pa zahteva ustrezno kodo (če sta nastavljeni dve metodi, se koda
preveri proti obema, glejte »Zagotovilo je lastnost seje, ne računa« zgoraj). Ob uspehu se sproži
prava jedrna prijava (auth_login()), brez lastne logike za sejo in piškotke, hkrati pa se
zabeleži zagotovilo seje (StepUp::recordAssurance()): dejavnik, dokazan ob prijavi, od tedaj šteje
enako kot dejavnik, dokazan z dodatnim preverjanjem, zato takojšnje, odvečno ponovno vprašanje ni
potrebno. Po login_max_attempts neuspelih poskusih se čakajoče stanje zavrže; uporabnik mora znova
začeti z geslom.
Uporabnikov brez vpisanega dejavnika to sploh ne zadeva – gateLogin() poseže samo pri
uporabnikih, ki dejavnik že imajo; račun, ki dvostopenjskega preverjanja nikoli ni nastavil, se še
naprej prijavlja v enem koraku, tudi pri vklopljenem enforce_login. Nastavitev torej nikogar ne
pripravi do vpisa, temveč zaostri samo prijavo računov, ki dejavnik že imajo.
enforce_login preglasi jedrno vedenje funkcije auth_login() – to je najbolj tvegan del kode v
tem vtičniku. Zato je privzeto izklopljen in dosegljiv kot preprosto stikalo onoff v običajnem
zaslonu »Splošne nastavitve« (ne samo v lastni skrbniški plošči): napake na tem mestu bi sicer v
najslabšem primeru ne bilo mogoče dovolj hitro izklopiti.
Postopki z enkratno kodo
Trije postopki, en mehanizem: ponastavitev gesla, samoregistracija in povabilo, ki ga pošlje skrbnik. Vsi trije se končajo tako, da imetnik računa geslo vnese sam – geslo se nikoli ne ustvari, nikoli ne pošlje in nikoli ne prikaže.
Vklopite in izklopite jih z otp_flows (privzeto vklopljeno).
Dve ključni spoznanji
Brez njiju je seznam nastavitev videti kot pet poljubnih časovnih vrednosti:
- Koda ni postopek. Koda velja
otp_ttlsekund (privzeto 1800), postopek sam pareset_ttl/register_ttl/invite_ttl(privzeto 1 dan / 2 dneva / 7 dni). Zato je mogoče zahtevati novo kodo, ne da bi začeli znova – poslano sporočilo se lahko izgubi, postopek ga preživi. Koda v poštnem predalu je ključ v vratih, zato je kratkotrajna; postopek ni. - Ugibanje omejuje proračun poskusov, ne dolžina kode.
otp_attempts(privzeto 5) šteje na postopek, nova koda ga ne napolni znova. Pritisk na »Pošlji novo kodo« ne prinese nove serije poskusov.otp_resend_gap(privzeto 60 s) dodatno omejuje, kako pogosto je kodo sploh mogoče poslati znova.
Kaj obrazec razkrije o računu
Nič. requestReset() odgovori enako, ne glede na to, ali račun obstaja – sicer bi bil obrazec
za ponastavitev preverjalnik članstva, v katerega lahko vsakdo vtipka poljubna imena.
Iz istega razloga je besedilo na zaslonu za vse enako: izpelje se samo iz stanja celotnega wikija, nikoli iz vnesenega imena. Besedilo, ki bi se prilagajalo posameznemu računu, bi izdalo, katera imena imajo aplikacijo za preverjanje.
Pot prek skrbnika
Za primer, ki ga noben obrazec ne more rešiti – recovery_factor ima vrednost totp, račun pa
nima aplikacije za preverjanje, ali pa e-pošta dokazano ne prispe:
- Skrbnik izda kodo v upravljanju povabil (Tehnična referenca) ali v ukazni vrstici. Ta pot namenoma odpre postopek s kodo po e-pošti, ne glede na
recovery_factor– je izhod v sili, in izhod, za katerega bi veljal isti pravilnik kot za pot v račun, ne bi bil izhod. - Koda se skrbniku prikaže natanko enkrat, v odgovoru na zahtevo, in ni nikjer shranjena tako, da bi se lahko prikazala še enkrat.
- Najkrajši razmik za ponovno pošiljanje tu ne velja: če bi morala pomoč čakati minuto samo zato, ker se je uporabnik zatipkal, bi to izničilo njen namen.
- Koda upravičuje do nastavitve gesla in nobenega ne razkrije. Skrbnik zato nima v rokah nobenih prijavnih podatkov. Ker pa koda dovoljuje nastaviti geslo, je njena predaja skrbnikova odgovornost: oseba, ki jo prejme, lahko računu nastavi novo geslo.
Varnost
- Šifriranje shranjenih podatkov – skrivnosti nikoli niso shranjene na disku v golem besedilu (Tehnična referenca: shranjevanje); kode za e-pošto so shranjene samo kot zgoščene vrednosti s soljo (od utrditve varnosti
password_hash()). - Ponovna uporaba kode TOTP je izključena (od utrditve varnosti) – časovni korak, ki je bil enkrat sprejet, se za isti račun ne sprejme še enkrat, na vseh mestih, ki uporabljajo
verifyCode()(prijava, dodatno preverjanje, potrditev vpisa, obnovitev gesla). - CSRF – vsaka oddaja na
do=profile, v skrbniški plošči in nado=wkidentity_verifyprevericheckSecurityToken()(CWE-352). - XSS – vse izpisane vrednosti (skrivnost, URI, prijavno ime) gredo skozi
hsc()(CWE-79). - Vpis v dveh korakih – preprečuje »vklop« brez dokazanega skeniranja oziroma prejema (glejte »Potek dela (vpis)«), za obe metodi.
- Obveznost ob prijavi kot zasilno stikalo –
enforce_loginje preprosto stikaloonoff, neodvisno od lastne skrbniške plošče, ki ga je mogoče takoj izklopiti. - Omejitev poskusov – števec neuspelih poskusov na
do=wkidentity_verify, vezan na sejo (brez sledenja naslovom IP, namenoma preprosto). - Orodje za razhroščevanje – dosegljivo samo prijavljenim uporabnikom; kode se izračunajo izključno v brskalniku, strežniku se nikoli nič ne pošlje.
- Prvi vtičnik
AUTH_ACL_CHECKv tem repozitoriju – pravice samo dodatno zaostri, datotekeconf/acl.auth.phpnikoli ne spreminja. - Nobeno geslo nikoli ne zapusti strežnika – postopki z enkratno kodo gesla ne ustvarijo in ne pošljejo; imetnik računa ga vtipka sam, najmanjšo dolžino pa preveri strežnik.
- Kode samo kot zgoščene vrednosti – tudi v datotekah postopkov; skrbnik lahko kodo izda, ne more pa je poiskati.
- Brez razkrivanja računov – obrazec za ponastavitev odgovori enako, ne glede na to, ali račun obstaja, in njegovo besedilo se nikoli ne izpelje iz vnesenega imena.
- Brez zapisovanja pri GET – vsaka veja postopkov, ki spreminja stanje, zahteva POST in varnostni žeton; zahteva GET samo izriše obrazec. Ponastavitev, ki bi jo lahko sprožil iskalni robot s sledenjem povezavi, bi lahko vsakdo sprožil za vsakogar.
Sorodne teme
- Identity – začetna stran in pogosta opravila
- Tehnična referenca – pogodba, vstopne točke, shranjevanje, konfiguracija, razredi
- Diagnostika – simptomi z vzrokom in rešitvijo
- Zaslon s pozivom za kodo – zakaj je zgrajen tako, kot je
- Workbench Plugin Contract v1 – njegovo polje
permissionta pogodba izpolni