Webbutvecklingsdagbok (Webbutveckling, 120 hp)

Permalänk
Medlem

Blir på riktigt orolig, nej livrädd, att efter flera års utbildning inte ens skapa relativt säkra formulär

Man kan ibland undra vad ni lärt er

Permalänk
Skrivet av medbor:

Blir på riktigt orolig, nej livrädd, att efter flera års utbildning inte ens skapa relativt säkra formulär

Man kan ibland undra vad ni lärt er

Ja, det är minst sagt pinsamt! Nämnde jag att vi har i vissa projekt använt oss av localStorage-tokens för att genomföra anrop inuti SPA? I MERN-stacks-kursen implementerade jag dock access & refresh tokens via httpOnly och inga localStorage-fuffens.

Fast det är mest jag som glömt basic stuff i vissa IActionResult-metoder vilket jag fixar nu - efter mycket uppskattad pen-test från erfaren programmerings-Discord-person - även fast jag redan lämnat in arbetet för slutgiltigt betygsättning. Det hela kan alltså kanske säga mycket mer om mig än själva utbildningen. Det får folk som kikar på mina tidigare kodprojekt avgöra.

Vad jag kan säga är att SecDev-delen har varit i denna Webbutvecklingsutbildning på distans att förhindra SQL-injektioner, XSS, CSRF och även övrig sanering av data utifrån kravspecifikation (t.ex. sanera bort (vissa) HTML-taggar om det inte ska få lagras). Också ett slags SecDev-översiktstänk har varit bristande såsom att exempelvis bestämma i förväg "Principle of Least Privilege" och skapa en pipeline av säkerhetskontroller i t.ex. PHP eller NodeJS som används konsekvent. MAO & TLDR: Noll arkitekturmässigt säkerhetstänk här mer än det redan ovannämnda.

En liten detalj jag fastnat på nu är beträffande id:n som skickas med från formulär. Anta att jag öppnar följande som verifierar så klart att jag äger annonsen:

http://localhost:5235/mina-annonser/7/redigera

Men hyptotetiskt skulle jag också kunna äga annons 8 och då kan jag ändra detta direkt i HTML-filen innan jag skickar iväg:

// .cshtml-fil <input type="hidden" asp-for="Id" /> // genererad HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="7"> // ändrar ren HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="8">

Så jag skulle då ha ändrat min annons dolda id-värde (value="7" till value="8") från formuläret för min annons 7 då ägandekontrollen skulle gå igenom. En lösning kanske vore att tillfälligt lagra något i databasen när jag kommer till en redigeringssida som det kan jämföras mot när jag skickar iväg och så då kan den neka att jag skickar uppdatering för annons 8 fast jag äger den.

Utan då måste jag klicka på Redigera-knappen för annons 8 eller besöka /mina-annonser/8/redigera vilket då uppdaterar i databasen igen för förberedelse att nu kan en uppdatering för annons 8 komma - självfallet kontrolleras ägandeskap precis som tidigare.

Jag kan knappast vara den första att ha tänkt ut lösningar på detta uppenbara formulärproblem? Så klart kan man ställa sig frågan varför någon som äger två annonser skulle försöka ändra sin andra annons genom att redigera ett dolt id-värde för sin nuvarande annons? 🤔

Mvh,
WKL.

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Ja, det är minst sagt pinsamt! Nämnde jag att vi har i vissa projekt använt oss av localStorage-tokens för att genomföra anrop inuti SPA? I MERN-stacks-kursen implementerade jag dock access & refresh tokens via httpOnly och inga localStorage-fuffens.

Fast det är mest jag som glömt basic stuff i vissa IActionResult-metoder vilket jag fixar nu - efter mycket uppskattad pen-test från erfaren programmerings-Discord-person - även fast jag redan lämnat in arbetet för slutgiltigt betygsättning. Det hela kan alltså kanske säga mycket mer om mig än själva utbildningen. Det får folk som kikar på mina tidigare kodprojekt avgöra.

Vad jag kan säga är att SecDev-delen har varit i denna Webbutvecklingsutbildning på distans att förhindra SQL-injektioner, XSS, CSRF och även övrig sanering av data utifrån kravspecifikation (t.ex. sanera bort (vissa) HTML-taggar om det inte ska få lagras). Också ett slags SecDev-översiktstänk har varit bristande såsom att exempelvis bestämma i förväg "Principle of Least Privilege" och skapa en pipeline av säkerhetskontroller i t.ex. PHP eller NodeJS som används konsekvent. MAO & TLDR: Noll arkitekturmässigt säkerhetstänk här mer än det redan ovannämnda.

En liten detalj jag fastnat på nu är beträffande id:n som skickas med från formulär. Anta att jag öppnar följande som verifierar så klart att jag äger annonsen:

http://localhost:5235/mina-annonser/7/redigera

Men hyptotetiskt skulle jag också kunna äga annons 8 och då kan jag ändra detta direkt i HTML-filen innan jag skickar iväg:

// .cshtml-fil <input type="hidden" asp-for="Id" /> // genererad HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="7"> // ändrar ren HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="8">

Så jag skulle då ha ändrat min annons dolda id-värde (value="7" till value="8") från formuläret för min annons 7 då ägandekontrollen skulle gå igenom. En lösning kanske vore att tillfälligt lagra något i databasen när jag kommer till en redigeringssida som det kan jämföras mot när jag skickar iväg och så då kan den neka att jag skickar uppdatering för annons 8 fast jag äger den.

Utan då måste jag klicka på Redigera-knappen för annons 8 eller besöka /mina-annonser/8/redigera vilket då uppdaterar i databasen igen för förberedelse att nu kan en uppdatering för annons 8 komma - självfallet kontrolleras ägandeskap precis som tidigare.

Jag kan knappast vara den första att ha tänkt ut lösningar på detta uppenbara formulärproblem? Så klart kan man ställa sig frågan varför någon som äger två annonser skulle försöka ändra sin andra annons genom att redigera ett dolt id-värde för sin nuvarande annons? 🤔

Mvh,
WKL.

Som du i alla fall upptäckt så är ju inte dolda värden dolda på riktigt. Man skulle kunna ge ut edit-token för enskilda annonser så man vet och kan validera vilken den hör till. Men också att validera användaren vid varje formulär/anrop så man vet att personen har rätt rättigheter att utföra ändringen

Permalänk
Skrivet av medbor:

Som du i alla fall upptäckt så är ju inte dolda värden dolda på riktigt. Man skulle kunna ge ut edit-token för enskilda annonser så man vet och kan validera vilken den hör till. Men också att validera användaren vid varje formulär/anrop så man vet att personen har rätt rättigheter att utföra ändringen

Jag har ju så här just nu i C#.NET ASP.NET Core MVC-kod beträffande Redigeringssidan:

// GET: Ads/Edit/5 [Authorize] // Must be logged in [Route("/mina-annonser/{id:int}/redigera")] public async Task<IActionResult> Edit(int? id)

Sen hämtar jag vald annons och kontrollerar att det är rätt ägare bakom den:

// Get the ad+images by id var ad = await _context.Ads .Include(a => a.AdImages) .FirstOrDefaultAsync(m => m.Id == id); // Check if the ad's UserId matches the UserId of the current user if (ad.UserId != currentUser.Id) { // Show default page when denied ViewData["ErrorType"] = "Åtkomst nekad!"; ViewData["ErrorMessage"] = "Du saknar behörighet att redigera denna annons!"; return View("DeniedOrNotFound"); }

För att POSTa redigerad annons uppgifter (ej bilder) så har jag först:

// POST: annonser/5/redigera [Authorize] [HttpPost] [ValidateAntiForgeryToken] // Check correct user made the request public async Task<IActionResult> EditSave(int id, [Bind("Id,Title,Description,Category,Price")] Ad ad)

Utöver CSRF-skyddet så är det denna kontroll som gör det sista jag kan göra:

// Load existing ad and current user var existingAd = await _context.Ads .Include(a => a.AdImages) .FirstOrDefaultAsync(a => a.Id == id); var currentUser = await _userManager.GetUserAsync(User); // Check if the ad exists and if the logged-in user owns it if (existingAd == null || existingAd.AdByUsername != currentUser.UsernameShown) { ViewData["ErrorType"] = "Hacker upptäckt!"; ViewData["ErrorMessage"] = "Du håller på med databashacking. Inte coolt!"; return View("DeniedOrNotFound"); }

Utöver Edit-tokens, äré inte CSRF-skyddet du syftade på?

Mvh,
WKL.

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk

Jag vill tacka så mycket för "den välmenade utskällningen" här och på Discord!

Jag har nu åtgärdat majoriteten av sårbarheterna såväl som tillfört en extra modell bara för träningens skull (en dag får jag nog betalt för det) vilket implementerat "MVP" av tokens för varje undersida som stödjer CRUD:

CRUDToken datamodell:

using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using projekt; namespace projekt; // CRUDToken model - Used to associate a token with a user and an ad for // making sure that the user can only edit their ads and that they cannot // change the ad Id in order to change either their own or someone else's ad. // They can also then only edit one of their ads at a time. It can also be done // To make sure a user can only CRUD their own ads and images! public class CRUDToken { public int Id { get; set; } // Foreign key (UserId) for ApplicationUser (User) public string? UserId { get; set; } [ForeignKey("UserId")] public ApplicationUser? User { get; set; } // Foreign key (AdId) for Ad public int? AdId { get; set; } [ForeignKey("AdId")] public Ad? Ad { get; set; } // Token for CRUD ads and/or images public string? Token { get; set; } }

Dold text

Kod i kontrollerfilerna:

// AddToken adds a token associated with current UserId and AdId and sends it to the user private string AddToken(int? adId, string? UserId) { // Remove all current tokens for current user's adId // This makes it so that if the user does open a new tab it will not be able to use the old token var tokens = _context.CRUDTokens.Where(t=> t.UserId == UserId); if(tokens != null){ _context.CRUDTokens.RemoveRange(tokens); _context.SaveChanges(); } // Then prepare a new CRUDToken with unique token (GUID) var token = new CRUDToken(); token.AdId = adId; token.UserId = UserId; token.Token = Guid.NewGuid().ToString(); // Add the token to the CRUDTokens table _context.CRUDTokens.Add(token); _context.SaveChanges(); // Return the newly generated token return token.Token; } // CheckToken checks if Current UserId has a token associated with AdId private bool CheckToken(int? adId, string? UserId, string? token) { return _context.CRUDTokens .Any(t => t.AdId == adId && t.UserId == UserId && t.Token == token); } // Remove one token associated with current UserId and AdId // This is used after a successful POST action has been performed private void RemoveToken(int? adId, string? UserId, string? token) { var removeToken = _context.CRUDTokens .FirstOrDefault(t => t.AdId == adId && t.UserId == UserId && t.Token == token); if(removeToken != null){ _context.CRUDTokens.Remove(removeToken); _context.SaveChanges(); } }

Dold text

Flödet är att varje gång du besöker en sida där du kan skapa, ändra och/eller radera utifrån givet id så skapas en GUID-genererad token som ligger "dolt" i ett inmatningsfält. Detta lagras också i databasen för nuvarande användare medan deras tidigare besök för andra CUD-sidor raderas ur samma databastabell.

Således kan du inte öppna flera flikar i webbläsaren för varje ny flik kommer att radera tidigare CRUDToken ur databastabellen. Efter lyckat CUD-anrop (vilket då gjort något i databasen för den verifierade användaren) så raderas samma CRUDToken för den användaren.

Sårbarheten som finns här är om du skulle få tag på CRUDToken och en användare har lämnat sidan men inte öppnat någon annan sida som genererar en ny CRUDToken och så skickar du rätt mot rätt API-ändpunkt. Så sista som fattas är väl datum som kan löpa ut? Detta kan dock väcka missnöje då det i princip skapar en "nedräkning" för hur fort användaren måste genomföra en viss CUD.

Men det var en bra ytterligare SecDev-grej att göra. Kan det betraktas som en variant av CSRF i och med att i denna modell+kontroller kontrollerar vi mot en användares sammankopplade annonser?

Mvh,
WKL.

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk

Ensam kodare är okunnig

Rubriken ovan syftar på att jag är utan teknisk handledare i mitt pågående exjobb och jag har redan stött på ett "otrevligt" problem med "vanilj SQL"-kodande. Nej, 🤖chatGPT🤖 har inte visat sig vara så hjälpsam när väl kommer till komplex SQL som inte heller verkar finnas på YouTube. Men, hur har det gått med de två övriga kurserna innan den numera pågående exjobb-kursen? 🤔

Webbutveckling med .NET har fått mig att respektera EF Core
Jag fick slutbetyg i kursen Webbutveckling med .NET och användningen av EF Core och snarare avsaknaden av den nu när jag är i fullrulle med exjobbet där jag använder MariaDB, vanilj SQL- & PHP-kod har fått mig att respektera den bekväma kraften med EF Core särskilt när du vill hantera 10 olika tabeller samtidigt bara för att din tidigare databaslärare ska ge tummen upp för all normalisering.

Det är helt otroligt att EF Core kan inkludera flera SQL-tabeller och sedan kan jag filtrera ut vad jag ska ha och magiskt kan den med sina "navigation properties" slå ihop iterationsbara data att presenteras för besökaren på webbplatsen. Nu när jag fått slutbetyget så finns min webbplats hos Microsoft Azure ej kvar längre. Den varade bara ett par dagar för lärarna var snabba på att rätta.

Väntar fortfarande på Affärsplaner & kommersialisering...
Den andra kursen så har läraren (under Deltagare) inte varit inne på över två veckor nu (15 dagar i skrivande stund). Möjligen kommer läraren att bara lägga upp allt direkt i Ladok så där kommer en då att få se slutgiltigt betyg. Jag har inte sett något ännu och inte hört från någon annan i klassen om att ha fått någonting rättat. Lärarna ska om jag minns rätt ha runt tre veckor på sig att hinna rätta. Ingen i klassen verkar ha fått något rättat och då har vissa lämnat in tidigare än andra.

L(/P-)oopia.se - Webbhotellet med dyra "domäntjänster" och idiotiska VPS-förslag
Vidare till exjobbet där jag - like a digital clout chaser - är utan någon teknisk handledare så när jag kör fast så måste jag inte bara besluta huruvida jag ska plöja igenom det jag fastnat i eller om jag ska välja en "snabbare" och därmed kanske "enklare" väg.

Samtidigt måste jag också meddela uppdateringar till exjobb-givaren vilket kanske kan väcka missnöje eller funderingar om hur "kompetent" jag egentligen är baserade på mina tidigare skrytsamma slutbetyg i tidigare kurser. Det är som om jag egentligen bara är duktig på att lösa skoluppgifter men inte faktiska verkliga problem.

Exjobb-givaren har Loopia - eller "Poopia" som jag numera kallar det för - som "sjösättningslösning". Det går inte att fixa Laravel där utan krångel eller utan riktig VPS vilket kostar 280 + moms per månad vilket jag tvekar starkt på att exjobb-givaren vill investera i. Exjobb-givare vill ju ha våra gratistjänster inte information om att de måste investera i bättre "sjösättningsteknologi" (eng. deployment technology)!

Poopia har också dyra domäntjänster (t.ex. konstanta DNS-avgifter vilket verkar ingå hos exempelvis Inleed - ej sponsrad!) så inte gör det saken bättre. Sedan läste jag på om deras VPS-förslag där de då föreslår att du köper VPS för att sedan installera WordPress på din VPS!? 🙃

Det var också lite krångel i början att få tillgång till phpMyAdmin-konto och enskilt databaskonto bara för att kunna ansluta till databasen. I mitt databaskonto vid universitetet så kunde jag använda samma konto för att komma in på phpMyAdmin såväl som att ansluta mot databasen. Att skilja på dessa är såklart bättre för då kan man radera behörigheter som DROP direkt för databasanvändaren och endast kan tabeller raderas genom att komma in på phpMyAdmin-kontot. Så inga klagomål där.

Vad i MariaDB/SQL är det då jag bråkar med?
För närvarande har jag 10 tabeller där två tabeller har med användare att göra med så de är inga större problem. Utmaningen kommer till tabeller med relationer där de också har olika mängder data. Nedanför har jag "fördunklat" vad de faktiska entiteterna heter och syftar på men deras relationsmässiga motsvarigheter överensstämmer rätt bra:

-- Every Car CREATE TABLE Cars ( id INT PRIMARY KEY AUTO_INCREMENT, car_description VARCHAR(500) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, sold_at TIMESTAMP DEFAULT NULL ); -- Every Car Has A number of Tires CREATE TABLE Tires ( id INT PRIMARY KEY AUTO_INCREMENT, car_id INT NOT NULL, tire_pressure INT NOT NULL CHECK (point BETWEEN -10 AND 10), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, filled_up_at TIMESTAMP DEFAULT NULL, FOREIGN KEY (car_id) REFERENCES Cars(id) ON DELETE CASCADE ); -- Categories to categorize each Car CREATE TABLE Categories ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(128) NOT NULL UNIQUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Attach several categories to the same Car CREATE TABLE CategoriesCars ( car_id INT, category_id INT, category_value VARCHAR(128) NOT NULL, FOREIGN KEY (car_id) REFERENCES Cars(id) ON DELETE CASCADE, FOREIGN KEY (category_id) REFERENCES Categories(id) ON DELETE CASCADE, PRIMARY KEY (car_id, category_id) );

Det är när jag vill SELECTa för att sedan kunna hämta alla bilar där varje bil sedan har alla antal däck kopplade till sig och sedan även har varje bil alla sina kopplade tilldelade kategorier. Men data som skapas i SQL är såklart massor av tomma fält med NULL och den kan till och med utesluta vissa rader med annars tillgänglig data för att den kanske inte fann någon kategori för en bil eller inga däck till en given bil fast bilen annars finns!

Detta är inget problem när EF Core magiskt med sina "navigation properties" definierade för alla databastabeller kan slås samman och sedan returnera klassisk JSON-aktig data (jag vet, det är inte riktigt det i C#.NET) utan att det skulle komma med massa NULL-värden här och var eller ännu värre: massa dubbletter.

Visst skulle jag kunna rekommendera exjobb-givaren att investera i Loopia VPS så jag får slänga upp C#.NET (ironiskt så kom strikt typat till undsättning trots att jag avskytt det under hela C#-kurserna fast kanske det är EF Core jag älskar?) så kommer det gå mycket smidigare med all SQL:ande.

Jag förstår inte hur riktiga systemdatabasadministratörer lyckas slå samman flera tabeller utan massor av dubbletter och/eller NULL-värden pga. olika antal kolumner i tabeller. Och hur gör då EF Core det? Den kan ju inte bara göra en supersmart query utan också någon rejäl databearbetning som på något vis verkar vara snabbare än hur kanske Eloquent i Laravel gör det?

Ja, hade Laravel gått att slänga upp så hade ju kanske Eloquent också löst en hel del. Det finns något som heter Medoo.in men det verkar dock inte erbjuda den där smarta bearbetningen av data som jag behöver utan underlättar bara queryn.

Vad har jag då för "ensam kodare är okunnig"-lösning då med MariaDB/SQL?
Den "gymnasieprogrammerar"-lösningen jag funderar då på är att nyttja mina styrkor: hantera JSON. Det går nämligen att - troligen högst prestandaineffektivt så det bara sjunger om det - hämta ut SQL-data som JSON_OBJECT som jag sedan kan bearbeta i renodlad PHP.

SELECT JSON_OBJECT( 'CarID', Cars.id, 'Description', Cars.car_description, 'CreatedAt', Cars.created_at ) AS json_cars, JSON_ARRAYAGG( JSON_OBJECT( 'CarID', Tires.car_id, 'TireID', Tires.id, 'Pressure', Tires.tire_pressure, 'CreatedAt', Tires.created_at ) ) AS json_tires FROM Cars LEFT JOIN Tires ON Cars.id = Tires.car_id GROUP BY Cars.id;

Koden ovan ger mig då två objekt att bearbeta där jag slår ihop alla däck med rätt CarID med rätt Cars.id. Men då kommer nästa krux: hur sjutton gör jag med alla kategorier som varje bil också har tilldelad? Då måste jag hämta alla kategorier ur Categories-entiteten och slå den samman med CategoriesCars och sen få in denna för varje rätt Cars.id.

Frågan är också då: Är det detta EF Core egentligen gör under ytan? Hur kan den annars skapa JSON-aktig data som inte innehåller dubbletter och/eller NULL-värden på grund av eventuella JOIN-klausuler? En idé jag har är att slänga in alla tabeller i ett C#.NET EF Core projekt och sedan försöka utläsa vad för SQL som körs i Terminalen och se om det går att anpassa samma SQL-körning i MariaDB. Men jag misstänker starkt att mer genomförs med den erhållna SQL-datan och inte bara en "magisk" SQL Query och vips så har vi JSON-aktig data att göra precis vad vi vill med!

Det svåraste med relationsdatabaser just nu - få ut exakta data från på hanterbart sätt!
Således, den största utmaningen jag har just nu med relationsdatabasen av typen MariaDB/SQL är att få de exakta data jag vill genom att kunna formulera "perfekta" SELECT queries med rätt JOINs, UNIONs eller vad nu som behövs. I EF Core är det verkligen mycket semantiskt trevligare.

Men jag undrar fortfarande om det sker mer efterbehandling på erhållna data i EF Core än vad man kan tro när man skriver sina LINQ-queries där. Hm... Den som nöter vidare får nog förr eller senare reda på saken!🕵️

På återseende!

Mvh,
WKL.
---------
✔️Kurs 1: HT2022 DT057G Datateknik GR (A), Webbutveckling I, 7,5 hp (distans)
✔️Kurs 2: HT2022 DT084G Datateknik GR (A), Introduktion till programmering i JavaScript, 7,5 hp (distans)
✔️Kurs 3: HT2022 DT068G Datateknik GR (B), Webbanvändbarhet, 7,5 hp (distans)
✔️Kurs 4: HT2022 DT200G Datateknik GR (A), Grafisk teknik för webb, 7,5 hp (distans)
✔️Kurs 5: VT2023 DT093G Datateknik GR (B), Webbutveckling II, 7,5 hp (distans)
✔️Kurs 6: VT2023 DT003G Datateknik GR (A), Databaser, 7,5 hp (distans)
✔️Kurs 7: VT2023 DT197G Datateknik GR (B), Webbdesign för CMS, 7,5 hp (distans)
✔️Kurs 8: VT2023 DT173G Datateknik GR (B), Webbutveckling III, 7,5 hp (distans)
✔️Kurs 9: HT2023 IK060G Informatik GR (A), Projektledning, 7,5 hp (distans)
✔️Kurs 10: HT2023 DT193G Datateknik GR (B), Fullstack-utveckling med ramverk, 7,5 hp (distans)
✔️Kurs 11: VT2023 DT162G Datateknik GR (B), Javascriptbaserad webbutveckling, 7,5 hp (distans)
✔️Kurs 12: VT2023 DT071G Datateknik GR (A), Programmering i C#.NET, 7,5 hp (distans)
✔️Kurs 13: VT2024 DT191G Datateknik GR (B), Webbutveckling med .NET, 7,5 hp (distans)
🚧Kurs 14: (Inväntar slutbetyg) VT2024 IG021G Industriell organisation och ekonomi GR (A), Affärsplaner och kommersialisering, 7,5 hp (distans)
🚧Kurs 15: (Exjobb pågår) VT2024 DT140G Datateknik GR (B), Självständigt arbete, 15 hp

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Är ditt problem alltså att du vill, för varje bil, även få ut dess egenskaper (alltså varje däck, categori m.m.)? För jag försöker förstå varför du blandar in JSON i din SQL-query

Du kan ju hämta ut alla bilar med dess tillhörande egenskaper genom LEFT JOIN

SELECT ... FROM Cars c LEFT JOIN Tires t ON c.id = t.car_id LEFT JOIN CategoriesCars ct ON c.id = ct.car_id LEFT JOIN Categories ca ON ct.category_id = ca.id ORDER BY c.id, t.id, ct.category_id

Du kommer få en "rad" tillbaka per egenskap såsom däck, kageori osv. Säg att du har 10 bilar, med 4 däck vardera (inga kategorier), det ger dig då 10 * 4 rader tillbaka.
Det är med denna data som du sedan bygger upp din datastruktur i PHP

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Skrivet av Pamudas:

Är ditt problem alltså att du vill, för varje bil, även få ut dess egenskaper (alltså varje däck, categori m.m.)? För jag försöker förstå varför du blandar in JSON i din SQL-query

Du kan ju hämta ut alla bilar med dess tillhörande egenskaper genom LEFT JOIN

SELECT ... FROM Cars c LEFT JOIN Tires t ON c.id = t.car_id LEFT JOIN CategoriesCars ct ON c.id = ct.car_id LEFT JOIN Categories ca ON ct.category_id = ca.id ORDER BY c.id, t.id, ct.category_id

Du kommer få en "rad" tillbaka per egenskap såsom däck, kageori osv. Säg att du har 10 bilar, med 4 däck vardera (inga kategorier), det ger dig då 10 * 4 rader tillbaka.
Det är med denna data som du sedan bygger upp din datastruktur i PHP

Tjo! Tack så mycket för svaret trots all min frustration!

Jag blandade in JSON_OBJECT för jag försökte hitta något annat sätt att göra det hela på. Så är på ett ungefär hur detta görs i relationsbaserade databasbranscherna: Du samlar på dig de data som du vill bygga en datastruktur av och så får du iterera igenom dubbletter och bygga upp en önskad datastruktur att sedan presentera (eller skicka vidare till) i frontend? Jag misstänker att även EF Core gör något liknande efter sin egen SQL-körning innan den skickar vidare de fantastiskt strukturerade data?

Jag ser oxå förresten att du kör tre sorteringar (ORDER BY) i slutet. Den första förstår jag, men hur kommer det sig att de två andra behövs? Bör inte de på något vis vara "bundna" till hur c.id sorteras som? Jag tänker när du sorterar utifrån en given kolumn inuti Excel. Eller blir det en specialare här för att de inte riktigt hör till samma tabeller?

Mvh,
WKL.

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Tjo! Tack så mycket för svaret trots all min frustration!

Jag blandade in JSON_OBJECT för jag försökte hitta något annat sätt att göra det hela på. Så är på ett ungefär hur detta görs i relationsbaserade databasbranscherna: Du samlar på dig de data som du vill bygga en datastruktur av och så får du iterera igenom dubbletter och bygga upp en önskad datastruktur att sedan presentera (eller skicka vidare till) i frontend? Jag misstänker att även EF Core gör något liknande efter sin egen SQL-körning innan den skickar vidare de fantastiskt strukturerade data?

Jag ser oxå förresten att du kör tre sorteringar (ORDER BY) i slutet. Den första förstår jag, men hur kommer det sig att de två andra behövs? Bör inte de på något vis vara "bundna" till hur c.id sorteras som? Jag tänker när du sorterar utifrån en given kolumn inuti Excel. Eller blir det en specialare här för att de inte riktigt hör till samma tabeller?

Mvh,
WKL.

Ja men typ.
I PHP är det hyffsat enkelt med arrayer, kolla om car.id existerar som nyckel - om inte, skapa objektet. Dvs: !isset(cars[car.id])

Om radens tire.id inte är null - lägg till i arrayen[car.id].tires
Gör samma med categories.

Ang. Order by så är det bara en preferens att sortera det så.
Fler kolumner i order by blir flera nivåer av sortering helt enkelt

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Medlem
Skrivet av medbor:

Blir på riktigt orolig, nej livrädd, att efter flera års utbildning inte ens skapa relativt säkra formulär

Man kan ibland undra vad ni lärt er

Det känns som att det är litet att kasta sten i glashus att dissa någon för ett sådant här fel när man själv inte klarar av att skriva två meningar på korrekt svenska...

Visa signatur

Bra, snabbt, billigt; välj två.

Ljud
PC → ODAC/O2 → Sennheiser HD650/Ultrasone PRO 900/...
PC → S.M.S.L SA300 → Bowers & Wilkins 607

Permalänk
Skrivet av Pamudas:

Ja men typ.
I PHP är det hyffsat enkelt med arrayer, kolla om car.id existerar som nyckel - om inte, skapa objektet. Dvs: !isset(cars[car.id])

Om radens tire.id inte är null - lägg till i arrayen[car.id].tires
Gör samma med categories.

Ang. Order by så är det bara en preferens att sortera det så.
Fler kolumner i order by blir flera nivåer av sortering helt enkelt

Jag ser nu att när jag tar ut "Bilar" med "Bildäck" och "Kategorier" så är det så att det finns exempelvis 12 olika "Kategorier" kopplade till en och samma "Bil" (Car_id) vilket då verkar göra så att för just en "Bil" då så får jag färst 12 rader med upprepade data från "Bil"-tabellen då övriga är LEFT JOIN. Det blir så för att just den Bil-Id har 12 referenser (Car_id) inuti Kategorier så det blir då 12 radmatchningar först? Här är också hela tiden "Tire_id = 1".

Sedan märker jag att efter 12 Kategorier så börjar den om igen för 12 Kategorier men nu på "Tire_id = 2" vilket nu blir det som upprepas 12 gånger såväl som alla övriga data från "Car"-tabellen. Jag har också fallet att en "Car" har 5st "Tire_id" kopplade till sig och en annan "Car" har 6st "Tire_id" kopplade till sig.

(5+6)(12) = 132 vilket stämmer överens med vad jag ser: 132 rader efter queryn. Det jag inte förstår är hur detta ens kan vara "hur man queryar SQL-databaser"-standard för nu har jag alltså bara 2 bilar, 11 däck och 13 kategorier inlagda och jag har redan 132 rader att plöja igenom. Hur blir det när det helt plötsligt är 1000 bilar med kanske 4000 däck?

Mvh,
WKL.

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Jag ser nu att när jag tar ut "Bilar" med "Bildäck" och "Kategorier" så är det så att det finns exempelvis 12 olika "Kategorier" kopplade till en och samma "Bil" (Car_id) vilket då verkar göra så att för just en "Bil" då så får jag färst 12 rader med upprepade data från "Bil"-tabellen då övriga är LEFT JOIN. Det blir så för att just den Bil-Id har 12 referenser (Car_id) inuti Kategorier så det blir då 12 radmatchningar först? Här är också hela tiden "Tire_id = 1".

Sedan märker jag att efter 12 Kategorier så börjar den om igen för 12 Kategorier men nu på "Tire_id = 2" vilket nu blir det som upprepas 12 gånger såväl som alla övriga data från "Car"-tabellen. Jag har också fallet att en "Car" har 5st "Tire_id" kopplade till sig och en annan "Car" har 6st "Tire_id" kopplade till sig.

(5+6)(12) = 132 vilket stämmer överens med vad jag ser: 132 rader efter queryn. Det jag inte förstår är hur detta ens kan vara "hur man queryar SQL-databaser"-standard för nu har jag alltså bara 2 bilar, 11 däck och 13 kategorier inlagda och jag har redan 132 rader att plöja igenom. Hur blir det när det helt plötsligt är 1000 bilar med kanske 4000 däck?

Mvh,
WKL.

Att "joina" tabeller är dyrt (väldigt dyrt) och ju fler tabeller du tar in, desto fler uppslag måste du därmed göra mot de andra tabellerna. Det är nackdelen med relationer helt enkelt. EF Core har precis samma begränsingar, bara att det inte sticker lika mycket i ögat Där har du även möjligheten att göra något som EF kallar Split Queries (Tänk dock på att SplitQueries inte alltid gör det snabbare)

Detta löses oftast med att begränsa den mängd data som faktiskt hämtas när du hämtar alla bilar. Ska du hämta all data så gör du det för en specifik bil, t.ex. en info-ruta som öppnas vid klick på bilen - eller använd någon form av paginering för att begränsa antalet rader som ska hämtas direkt.
Är tanken att du ska kunna hämta ut "antalet däck" som en bil har? Ja men kör något i stil med

SELECT COUNT(*) FROM Tires WHERE [CarId] = *bilens ID*

istället för att hämta ut all data. Att endast räkna antalet träffar är mycket snabbare.

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Skrivet av Pamudas:

Att "joina" tabeller är dyrt (väldigt dyrt) och ju fler tabeller du tar in, desto fler uppslag måste du därmed göra mot de andra tabellerna. Det är nackdelen med relationer helt enkelt. EF Core har precis samma begränsingar, bara att det inte sticker lika mycket i ögat Där har du även möjligheten att göra något som EF kallar Split Queries (Tänk dock på att SplitQueries inte alltid gör det snabbare)

Detta löses oftast med att begränsa den mängd data som faktiskt hämtas när du hämtar alla bilar. Ska du hämta all data så gör du det för en specifik bil, t.ex. en info-ruta som öppnas vid klick på bilen - eller använd någon form av paginering för att begränsa antalet rader som ska hämtas direkt.
Är tanken att du ska kunna hämta ut "antalet däck" som en bil har? Ja men kör något i stil med

SELECT COUNT(*) FROM Tires WHERE [CarId] = *bilens ID*

istället för att hämta ut all data. Att endast räkna antalet träffar är mycket snabbare.

Vore en lösning då med när man vill hämta "allt" om en given entitet att köra på LIMIT och köra med paginering? (EDIT: läste det nu det du skrev! ) Många webbplatser när du söker har ju paginering vilket jag antar delvis beror på detta faktum med databaser? Annars undrar jag hur företag har löst det hela med "Big Data" eller bara något där du kanske har kanske 100 kolumner i en Excel-fil med sedan tiotusentals rader? (eller vad som nu krävs för att det ska börja "kännas" lite) Då kanske inte ens relationsdatabaser används eller så kanske det implementeras med färre tabeller och därmed relationer?

När jag lärde mig grundläggande om databaser så var det fokus på att normalisera vilket i grunden verkar handla om att ha så många tabeller som möjligt för att förhindra motsägelser i databasen. Eller som en annan databaslärare har uttryckt som jag hört på YT: "Väx på längden!" (skapa ny tabell och/eller lägg till ny rad) istället för "växa på bredden" (lägga till ny kolumn i redan existerande tabell). Och detta verkar ju vara fint i teorin men i praktiken verkar det bli en "prestandakatastrof" när du ska sätta ihop flera tabeller för att du vill få en rejäl överblick men du har normaliserat bort nästan allt till enskilda tabeller?

Alternativ kanske är att hämta en tabell med alla Bilar och en tabell med alla Däck och så skickar man vidare all den data till någon annan processor som får sy ihop all data precis som jag kommer att behöva göra nu i PHP? Eller blir det egentligen samma sak i slutändan om man slår ihop den prestanda som då krävs från först databasen och sedan bearbetningen i PHP?

Jag antar att hur du än väljer att göra så kan du inte få allt!

Mvh,
WKL.

Visa signatur

"Den säkraste koden är den som aldrig skrivs"

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Alternativ kanske är att hämta en tabell med alla Bilar och en tabell med alla Däck och så skickar man vidare all den data till någon annan processor som får sy ihop all data precis som jag kommer att behöva göra nu i PHP? Eller blir det egentligen samma sak i slutändan om man slår ihop den prestanda som då krävs från först databasen och sedan bearbetningen i PHP?

Jag antar att hur du än väljer att göra så kan du inte få allt!

Mvh,
WKL.

Ja det är också ett alternativ. Men då sätter du högre krav på applikationen som bearbetar datan.
Tänk även på att din roundtrip till databasen kostar mycket i tid.

Stored procedures eller vyer i databasen är antagligen att rekommendera här.

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Medlem

Det jag som lekman förstått så ska databasen göra så mycket så möjligt. Det är det den är bäst på, att hantera data.

Sedan finns det ju index och saker man kan optimera i större databaser för att gynna vanligt förekommande frågor.

Permalänk

Sista Inlämningsrapporten för Webbutvecklingsutbildningen 120hp har påbörjats!

Videoklippet ovan beskriver lite hur det har varit under hela Webbutbildningens gång: du provar något, du blir nedslagen av okunnighet, nedslagen av oförmåga att kunna ta sig till information på internet, men du fortsätter att ta dig upp och gå vidare. Sedan kommer nästa nedslag och det fortsätter hela tiden men det viktiga är att hela tiden stiga upp och fortsätta framåt mot det du vill!

Exjobbgivaren - vad för webbplats har jag gjort?
Den senaste månaden har jag köttat exjobb-kodande med veckovisa avstämningsmöten där jag visat exjobbgivaren vad jag gjort såväl som demonstrerat och instruerat hur exjobbgivaren ska kunna använda slutprodukten vilket är en webbplats som jag nu kan prata lite mer öppet om då jag fått det klargjort vad jag kan prata om.

Exjobbgivarens webbplats är en administrativ portal (känns det bekant från mina tidigare kurser? ) där exjobbgivarens medarbetare ska kunna logga in och administrera något som kallas för "Situationer". En Situation har en beskrivning och färst ett så kallat Alternativ. Varje Alternativ har alltid en text samt ett poängvärde på max +10 och högst -10. Exjobbgivaren har krävt att det ska stå + framför positiva heltal förutom 0.

Varje Situation har utöver sin beskrivning också språk, versionsnummer, datum för skapande av exjobbgivaren, datum för införande i databas, kommentarer och referenser. Dessa är alltså kolumner som ska föras in i databas för varje införd Situation. Men det slutar inte där! Varje Situation har också elva olika kategorier där varje kategori i sin tur är en särskild typ med eget textfält. Exempelvis finns det en kategori som heter "Author" och innehåller information om författaren bakom Situationen.

Varje Situation ska gå att skapa, visa enskilt, ändra, och raderas. Visst låter denna CRUD också bekant från tidigare kurser? Vem eller vilka ska då ha nytta av dessa Situationer? Här kommer då den andra entiteten in i bilden: Partners. Varje Partner har ett företagsnamn, ett fullständigt namn, en e-postadress, ett telefonnummer, ett lösenord, och ett par övriga utelämnade fält.

En Partner ska kunna skapas, visas enskilt, ändras, raderas samt kunna tilldelas olika Situationer att kunna söka på. Det är vad Partners kan göra: logga in på portalen och sedan söka efter Situationer genom att välja mellan 2-4 kategorier med valfri söktext med begränsade inmatningslängder.

Administratörer internt i webbplatsen ska kunna söka på Partners och/eller Situationer och sedan kunna se vilka Situationer en enskild Partner har tilldelats redan så detta kan ändras på om så önskas. Liknande för en visad enskild Situation: du går in på den och du ser alla Partners som har fått Situationen tilldelad.

En Tilldelad Situation betyder alltså att Partnern som tilldelats denna kommer att kunna få upp denna bland sina sökresultat när de söker efter Situationer utifrån 2-4 kategorier med valfria inmatningsbegränsade söktexter.

I och med att det kommer att föras in ett par hundratals Situationer så önskades det även att kunna visa begränsat antal Situationer, 20 styck, per sida och därmed behövdes paginering implementerat internt.

Till sist så kan administratörer ändra sina lösenord.

Exjobbgivarens webbplats - hur har jag gjort den?
Om du följt min Webbutvecklingsblogg sedan start så kanske du slogs av tanken att det hela är ju relativt simpelt om än kan ta en hel del tid. Det är ju bara en administrativ portal med "100 % CRUD!😎"

Jag kände detta med så därför behövde jag inkludera något genomsyrande som även la grunden till att ha något mer att skriva om i den kommande exjobb-rapporten vilket ska vara färdig nu den 20:e maj 2024 och lämnas in till handledare för att få godkänd eller kompletteringskrav innan redovisning veckan därpå. Vad valde jag då för att ha något genomsyrande? Något som varit på tapeten hel del de senaste veckorna nu inom cybervärlden: SÄKERHET.

Jag tog mig an att lära mig flertalet saker inom webbsäkerhet såsom XSS, CSRF, SQL Injections, HTTP Headers, Host Header Injections, HTTP Response Splitting, Session Management, CSP Management, inmatningsvalidering & inmatningssanering, databassäkerhet, och några saker till - samt hur jag kan skydda mig mot vissa saker och andra säkerhetsåtgärder.

Innan jag nu visar så måste jag klargöra innan du frågar: nej, jag har inte implementerat Laravel eller något annat PHP-ramverk. Jag har använt mig av HTML, CSS (TailwindCSS) och PHP där jag själv byggt upp ett slags "funktionsbaserat ramverk" fast ändå inte eftersom jag inte har någon startpunkt (t.ex. routing) då jag inte har någon sådan filsystemåtkomst hos min exjobbgivare hos deras webbhotelleverantör Loopia.

Att ha byggt då från grunden med allt som det då kräver har varit mycket givande och troligen mer givande än om jag hade fått allt "gratis" som Auth Sanctum, Eloquent ORM, etc. i Laravel. Eller så är det bara jag som rationaliserar galenskaperna som pågått den senaste månaden!

Här är då ett exempel på den första koden som körs innan någon HTML visas i en given PHP-fil:

// Example of each PHP file that intend to show some HTML! // First we include lots PHP files which won't be shown here! AllowedMethodandContent('GET'); // 1 AllowedPOSTandGET(['CSRF'], []); // 2 setAndRemoveHeaders(); // 3 redirectToHttps(); // 4 init_session(); // 5 start_session(); // 6 onlyAdmin(); // 7

Jag går nu igenom kortfattat om de olika funktionerna numrerade i kommentarerna. Genomgående är principen om "Least Privilege". Så för att ens kunna få göra något på en given PHP-sida så måste vi ange vad som tillåts annars utgår vi ifrån att allt nekas.

AllowedMethodandContent('GET'); // 1

Kommentar 1 syftar på vad för HTTP-metod som anropas mot PHP-filen. Här tillåts endast 'GET'-anrop så skickar du POST, PUT osv så kommer du inte fram. Skulle du skriva in 'GET','POST' så kommer den stödja 'GET' och 'POST' men du kommer inte fram ändå med POST av ett skäl.

Du måste ange vad för slags datatyp som denna 'POST' ska acceptera. Skriv om till: 'GET', 'POST=application/x-www-form-urlencoded' så blir det rätt. 'GET' kollar automatiskt efter text/html som standard men godtar också om du skriver in 'GET=text/html'. Detta är då första steget. För om anropet inte ens är tillåtet finns det ingen anledning att ens överhuvudtaget överväga inkomna data i $_POST- och $_GET-variablerna.

AllowedPOSTandGET(['CSRF'], []); // 2

Kommentar 2 syftar på vad för slags data erhållet i $_POST och $_GET - uppdelat i varsin array i argumenten: (['CSRF'],[]) - som ska tillåtas och alla strängar som matas in genomgår en saneringsfunktion som tar bort specialtecken som skulle kunna krascha servern och/eller mata in skadlig kod som exempelvis \0, \r\n, %0D%0A. Även alla skalkommandon i PHP saneras bort. Men innan detta ens inträffar så körs strip_tags och det avslutas med htmlspecialchars. Saneringsfunktionen fungerar oavsett blAndning av BOKSTävsSTORLEKar.

Tittar vi vad som är inmatat så är det 'CSRF' för $_POST och inget för $_GET. Det betyder att fältet som heter 'CSRF' kommer att saneras och övriga $_POST & $_GET variabler kommer att försvinna via unset(). Här har jag också lagt in extra funktionalitet så jag kan skriva exempelvis: 'Ctest=1' vilket då tolkas att det finns ett formulärfält som heter "test" som ska ha värdet 1 annars kommer den variabeln att konsekvensutsättas av unset().

Men nu kommer en klurig grej: Hur blir det när jag vill ha begränsat antal av tillåtna data men som bygger på vad som finns lagrat i databasen? Lösningen är simpel: hämta dessa data från databas, lägg i en array med "Cfält=förväntaddata"-syntax och sprid sedan ut dem i AllowedPOSTandGET-funktionen. Fungerar fortfarande!

Det finns sedan två andra syntaxer: '!password' betyder att ett fält som heter 'password' kommer inte att saneras och innan du skriker så är det för att detta värde alltid kommer att hashas innan det används till något där det annars skulle kunna "köras".

Andra varianten heter "Bsubmit" vilket betyder att det finns en knapp vars name-värde är "submit". När det finns så raderas allt värde för den $_POST/$_GET-variabeln för vi är bara intresserade av att den finns för den signalerar att "du har klickat på en knapp som heter submit" till resten av PHP-filen.

Jag har också "lyxfunktionen" som hjälper till att sprida ut alla options och deras förväntade värden inuti en tillåten select inuti AllowedPOSTandGET-funktionen. Liknande skulle jag ha kunnat implementera för grupperade kryssrutor, men då jag aldrig använde mig av grupperade kryssrutor så blev det aldrig på tal.

Till sist om kommentar 2 så får du inte glömma bort att detta är vad jag skriver in i funktionen, inte vad som erhålls $_POST och $_GET från cybervärlden och/eller kosmisk strålning. Det som inte matas in i funktionen manuellt kommer aldrig med. "Least Privilege" som sagt.

(Ja, ALLA SQL-funktioner saneras mot SQL-injektioner! - Det finns det en funktion för!)

setAndRemoveHeaders(); // 3

Kommentar 3 syftar på precis det du tänker på: Headers som skickas med i HTTP Response tillbaka till mottagaren/besökaren. Här börjar jag då att försöka ta bort headers() som skulle kunna avslöja men här har Loopia med flit överskridit så även om jag tog bort "Server" och/eller "X-Powered-By" så lägger Loopia ändå - som har sista ordet bland headers() vid varje HTTP-respons - till Server för att berätta minsann för hackers vilken servermjukvara de kör...🙄

Här sätts headers såsom: Content-Type, Content-Security-Policy (där vi börjar med att nollställa allt för att följa "Least Privilege" och sedan lägger vi till det som tillåts få "köras" av mottagarens webbläsare), X-frame-options, X-content-type-options, x-xss-protection (denna är jag medveten om att den ska ej betraktas som "gör bara detta mot xss och tacka för dagen" utan som en av många små saker mot xss-attacker), x-permitted-cross-domain-policies, referrer-policy, cross-origin-resource-policy, Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy, Expect-CT, och Strict-Transport-Security.

Berätta gärna om jag missat någon "jätteviktig" header() jag borde inkludera. Jag kikade exempelvis på några Sweclockers hade och tog mig till en eller två därifrån också!

redirectToHttps(); // 4

Kommentar 4 syftar på något simpelt men ack så viktigt och något som jag tolkar som nästintill helt avgörande i många fall. För vad spelar headers() för roll om du kan drabbas av "Man-In-The-Middle" som då bara ändrar alla dina headers() innan de skickas vidare till slutanvändaren?

Funktionen kollar helt enkelt om du har HTTPS annars skickas du vidare till HTTPS-varianten även om header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload") troligen gör liknande sak? 🤔

init_session(); // 5

Nu har vi kollat tillåten HTTP-metod och tillåtna OCH sanerade inmatningsdata lagrade i $_POST och $_GET. Det är då dags att starta sessionen för att följa säker Session Management utlagt av OWASP Cheat Sheet Series såväl som ett par punkter från php.net i egen hög webbplats.

Det finns dock 2 rader som jag inte förstår mig på riktigt:

ini_set('session.cache_limiter', 'public'); // Prevents caching of the session pages session_cache_limiter(false); // Prevents caching of the session pages

Dessa två hittade jag på StackOverflow där jag hade problemet med "Document expired" i webbläsaren om jag gick tillbaka från ett lyckat POST-anrop som ledde till en annan sida (t.ex. lagt till något). Något med cache:n orsakade detta.

Övriga saker som sker i init_session() är olika inställningar via ini_set() som körs så att varje PHP-fil alltid nyttjar sessioner på korrekt sätt. Här sätts saker som livslängd, längd- och entropivärden på sessionsfilerna vilket lagras annorlunda hos Loopia än lokalt hos mig.

Det sista denna funktion gör innan start_session() får köras är att sätta parametrarna för kakor som ska användas lokalt respektive i produktion. Kakorna följer maximal strikthet enligt OWASP Cheat Sheet Series.

MEN! Det finns en sak jag måste fråga:

safeCOOKIES()

Denna funktion körs aldrig trots att den finns. Vad den gör är att den skulle sanera kakor genom att nollställa dem genom setcookie() så jag måste välja vilka kakor som jag ska bry mig om erhållna från $_COOKIES-arrayen. Frågan är om det gör någon nytta dock? Det skulle följa "Least Privilige" då antar jag? 🤔

start_session(); // 6

Kommentar 6 syftar då på att faktiskt starta session - äntligen - för en given PHP-fil. Nu kan PHP göra sin magi och kontrollera om det redan finns samma session som via erhållen kaka från klienten och jämföra annars startas en ny.

Frågan här är om jag ska vidareutveckla start_session-funktionen lite fort:

if (!session_id()) { session_start(); }

Jag skulle kunna göra så att den kör: "session_regenerate_id(true);" var 30:e minut? Frågan är om detta då skulle innebära att jag i princip gör kakan "odödlig" genom att den var 30:e minut får tillbaka sin långa annars bestämda livslängd? 🤔

Berätta gärna om jag bör lägga till session_regenerate_id(true)-funktionen som ett sätt att bekämpa Session Hijacking mer än vad jag redan gör.

check_login(); // 7 onlyAdmin(); // 7 RedirectFromLogin(); // 7

Kommentar 7 syftar på autentiserings- och/eller auktoriseringskontrollerna som görs på alla sidor förutom inloggningssidan eftersom alla övriga sidor kräver autentisering och/eller auktorisering. Observera också att jag visade "CSRF" i en av kommentarerna så även CSRF används på alla sidor med CRUD.

I slutskedet av kodandet så insåg jag att jag behöver bara kontrollera om användaren är inloggad som administratör annars blir det onödig dubbelkontrollering. Kontroller sker både av sessionsvariabler och vad som är lagrat i databas.

Gällande CSRF så är faktumet att det är så strikt att en Partner endast kan visa Tilldelade Situationer som de lyckats söka fram (en del av restriktionen beror faktiskt på önskemål från exjobbgivaren och gick då hand i hand med säkerhetstänket) som de sedan klickat på. Och trots det så kontrolleras det ändå om Partnern fortfarande har den framklickade Situationen tilldelad till sig eller inte innan den till sist visas.

När administratörer visar enskilda situationer så kan de kopiera dessa till Urklipp eller ladda ned dem som .txt-filer. Här fick jag då lära mig en ny header() som jag aldrig hade hört talas om under hela min Webbutvecklingsutbildning:

header('Content-Disposition: attachment; filename="text.txt");

Detta gav mig då idéer och insikter om hur det kanske fungerar med "skyddade" filnedladdningar där du bara får ladda hem filer om du kanske är inloggad då vad som skickas till dig som respons är filen och inte någon renderad HTML?

Så... Det var kortfattad överskådlig beskrivning av vad majoriteten av PHP-filerna som ska rendera HTML går igenom innan någon HTML ens visas. Och så har vi det här med att behandla knapptryck och POST och GET när något kanske söks eller läggs till så det måste hanteras innan något visas. Det var jag som tur var van vid även om det ibland kunde kännas lite "baklänges".

Exjobb-rapporten kvarstår
Jag valde den vägen att jag först ville implementera allt innan jag skulle påbörja rapporten. Jag är galen när det gäller rapporter: en rapport skrev jag på bara 11 timmar en natt och klarade galant. Denna rapport är egentligen ingen skillnad förutom högre och striktare krav på språkriktighet i enighet med B-kursnivå i egenskap av Självständigt arbetsrapport.

Vad jag troligen kommer att få kommentarer om från min handledare är tempusanvändningen och kanske vissa formuleringar. Jag läste förresten en tidigare rapport från någon annan färdigutbildad student som skrivit en rapport på 100+ sidor! 🤯

Jag siktar på att bli färdig med rapporten på bara 7-10 dagar och göra det motsatta en lärare sa under introduktionsföreläsningen för sista kursen:"Det har aldrig hänt!"

Då tänkte jag bara:"Håll min, vattenflaska!"🤣 och förhoppningsvis blir resten historia mycket snart - om cirka 7-10 dagar från och med i morgon!

Lokal redovisning med virtuella klasskamrater
Redovisningen kommer att äga rum på universitetet någon gång under veckan efter nästnästa vecka så inte denna vecka men om två veckor efter denna vecka. Knäck den tankenöten chatGPT3.5!😬

Då kommer jag att få träffa 5 klasskamrater som tidigare bara varit virtuella. Tre har jag redan träffat vid ett evenemang vid universitetet under Exjobbsletandet för flera veckor sedan. I skrivande stund är vi 34 kvar i programmet. Om jag minns rätt så var det drygt 20 styck kvar förra årskullen - men vi kanske också var fler i början jämfört med föregående kull under första läsåret?

Utbytt kurs efter Fx
Vad har hänt? Det står Marknadsföring från 2014 nu??? Det är som så att jag fick Fx på världens tråkigaste och sämsta upplagda kurser (inte bara enligt mig) så därför bytte jag ut den kursen mot en tidigare färdig kurs efter jag samtalat med en av programansvariga - det var inga problem.

En kuriosa om den sämsta kursen under hela Webbutvecklingsutbildningens gång är följande: Läraren hade tidigare fått negativ kursutvärdering men av någon outgrundlig anledning så såg bara läraren detta och inte någon programansvarig.

Nu kommer denna kurs dock inte ingå längre i programschemat för den som funderar på att gå denna utbildning. Även WordPress-kursen kommer att utgå. På tal om utbildningen så kommer jag att skriva en recension om Webbutvecklingsutbildningen 120 hp efter att jag har redovisat mitt Självständiga arbete vilket då blir någon gång i slutet på maj 2024.

Ett 99 % procedurbaserat PHP-ramverk?!
En sak som slog mig under tiden jag kodade åt exjobbgivaren där jag byggde upp ett simpelt system för att återupprepa på ett säkert och strukturerat vis var att jag kan ju bygga ett slags procedurbaserat PHP-ramverk som nyttjar funktioner istället för klassbaserat. Klasser i all ära men det är så överflödigt samtidigt tycker jag. Det jag inte vet dock är om klassbaserat (OOP helt enkelt) drar mer CPU och/eller minnen jämfört med procedurbaserat?

En plats där jag tycker klassbaserat är klockrent oavsett språk är hantering av erhållna data från databaser. Då är det skönt att inte behöva komma ihåg vad som ingick i en datasamling vid utskrift. Att kunna skriva "data->fält1" är skönare än att gissa sig fram "data['fält'1]".

Hursomhelst är min tanke att fixa en .htaccess-fil hos mitt webbhotell jag använder privat för om jag kan komma åt filer utanför "public_html"-mappen som endast servern får köra så kan jag omdirigera alla REQUEST_URIs till index.php i "public_html"-mappen som sedan kör kod under den mappen? Då kan jag ju skapa mitt eget lilla "procedurbaserade PHP-ramverk" och göra alla webbutvecklare förbannade över ett ytterligare ramverk! 😂

Det var då troligen den sista genomgående presentationen av mitt sista inlämnade kodarbete i denna Webbutvecklingsutbildning 120 hp 2022-2024. Nästa inlägg här blir om rapporten och/eller redovisningen såväl som kanske lite "blandat skvaller"?!😝

På återseende!

Mvh,
WKL.
---------
✔️Kurs 1: HT2022 DT057G Datateknik GR (A), Webbutveckling I, 7,5 hp (distans)
✔️Kurs 2: HT2022 DT084G Datateknik GR (A), Introduktion till programmering i JavaScript, 7,5 hp (distans)
✔️Kurs 3: HT2022 DT068G Datateknik GR (B), Webbanvändbarhet, 7,5 hp (distans)
✔️Kurs 4: HT2022 DT200G Datateknik GR (A), Grafisk teknik för webb, 7,5 hp (distans)
✔️Kurs 5: VT2023 DT093G Datateknik GR (B), Webbutveckling II, 7,5 hp (distans)
✔️Kurs 6: VT2023 DT003G Datateknik GR (A), Databaser, 7,5 hp (distans)
✔️Kurs 7: VT2023 DT197G Datateknik GR (B), Webbdesign för CMS, 7,5 hp (distans)
✔️Kurs 8: VT2023 DT173G Datateknik GR (B), Webbutveckling III, 7,5 hp (distans)
✔️Kurs 9: HT2023 IK060G Informatik GR (A), Projektledning, 7,5 hp (distans)
✔️Kurs 10: HT2023 DT193G Datateknik GR (B), Fullstack-utveckling med ramverk, 7,5 hp (distans)
✔️Kurs 11: VT2023 DT162G Datateknik GR (B), Javascriptbaserad webbutveckling, 7,5 hp (distans)
✔️Kurs 12: VT2023 DT071G Datateknik GR (A), Programmering i C#.NET, 7,5 hp (distans)
✔️Kurs 13: VT2024 DT191G Datateknik GR (B), Webbutveckling med .NET, 7,5 hp (distans)
✔️Kurs 14: VT2014 FÖ032G Företagsekonomi GR (A), Marknadsföring 7,5hp
🚧Kurs 15: (Exjobb snart klar) VT2024 DT140G Datateknik GR (B), Självständigt arbete, 15 hp (distans)

Visa signatur

"Den säkraste koden är den som aldrig skrivs"