Welkom terug bij Singing with Sinatra! In dit derde en laatste deel breiden we de app "Recall" uit die we in de vorige les hebben gebouwd. We gaan een RSS-feed toevoegen aan de app met de ongelooflijk handige Builder-edelsteen, wat het maken van XML-bestanden in Ruby een fluitje van een cent maakt. We zullen leren hoe gemakkelijk Sinatra HTML-code verwijdert van gebruikersinvoer om XSS-aanvallen te voorkomen, en we zullen sommige van de foutafhandelingscode verbeteren.
De algemene regel bij het bouwen van web-apps is paranoïde zijn. Paranoïde dat al je gebruikers je willen pakken door je site te vernietigen of door andere gebruikers aan te vallen. Voeg in je app een nieuwe notitie toe met de volgende inhoud:
woops
Momenteel zijn onze gebruikers vrij om elke gewenste HTML in te voeren. Hierdoor blijft de app open voor XSS-aanvallen waarbij een gebruiker mogelijk kwaadaardige JavaScript-code kan invoeren om andere gebruikers van de site aan te vallen of te misleiden. Dus het eerste dat we moeten doen is escpe alle door gebruikers ingezonden inhoud, zodat de bovenstaande code zal worden omgezet in HTML-entiteiten, zoals zo:
woops
Om dit te doen, voeg het volgende blok van code toe aan uw recall.rb
bestand, bijvoorbeeld onder de DataMapper.auto_upgrade!
lijn:
helpers omvatten Rack :: Utils alias_method: h,: escape_html end
Dit omvat een reeks methoden die door Rack worden geboden. We hebben nu toegang tot een h ()
methode om te ontsnappen aan HTML.
Om te ontsnappen aan HTML op de startpagina, opent u de views / home.erb
bekijk het bestand en wijzig het <%= note.content %>
regel (rond regel 11) naar:
<%=h note.content %>
Als alternatief zouden we dit zo kunnen hebben geschreven <%= h(note.content) %>
, maar de bovenstaande stijl komt veel vaker voor in de Ruby-community. Ververs de pagina en de ingediende HTML moet nu worden geëscaped en niet door de browser worden uitgevoerd:
Klik op de koppeling "bewerken" voor de notitie met de XSS-code. Mogelijk denkt u dat het veilig is - het zit allemaal in een tekstgebied en wordt dus niet uitgevoerd. Maar wat als we een nieuwe notitie met de volgende inhoud hebben toegevoegd:
Bekijk de bewerkpagina ervan en je kunt zien dat we het tekstgebied hebben afgesloten en daarom wordt de JavaScript-waarschuwing uitgevoerd. Het is dus duidelijk dat we moeten ontsnappen aan de inhoud van de notitie op elke pagina waar deze wordt weergegeven.
In jouw views / edit.erb
bekijk het bestand, ontsnap aan de inhoud in de textarea
door het door te voeren h
methode (regel 4):
En doe hetzelfde in uw views / delete.erb
bestand op regel 2:
Weet je zeker dat je de volgende opmerking wilt verwijderen: "<%=h @note.content %>"?
Daar heb je het - we zijn nu veilig voor XSS. Vergeet niet om te ontsnappen aan alle door gebruikers ingediende gegevens bij het maken van andere web-apps in de toekomst!
U vraagt zich misschien af "wat met SQL-injecties?" Welnu, DataMapper handelt dat voor ons af, net zo lang als we de methoden van DataMapper gebruiken voor het verkrijgen van gegevens uit de database (dwz het niet uitvoeren van onbewerkte SQL).
Een belangrijk onderdeel van een dynamische website is een of andere vorm van RSS-feed, en onze Recall-app zal geen uitzondering zijn! Gelukkig is het dat ongelooflijk gemakkelijk om feeds te maken dankzij het juweeltje van de bouwer. Installeer het met:
gem install builder
Afhankelijk van hoe u RubyGems heeft ingesteld op uw systeem, moet u mogelijk een voorvoegsel gebruiken gem installeren
met sudo
.
Voeg nu een nieuwe route toe aan uw recall.rb
aanvraagbestand voor een GET-aanvraag voor /rss.xml
:
krijg '/rss.xml' doe @notes = Note.all: order =>: id.desc builder: rss end
Zorg ervoor dat je deze route ergens toevoegt bovenstaande de krijg '/: id'
route, anders een aanvraag voor rss.xml
zou worden aangezien voor een post-ID!
In de route vragen we eenvoudig om alle notities uit de database en laden we a rss.builder
bestand bekijken. Merk op hoe eerder we de ERB-engine gebruikten om a weer te geven .erb
bestand, nu gebruiken we Builder om een bestand te verwerken. Een Builder-bestand is meestal een normaal Ruby-bestand met een special xml
object voor het maken van XML-tags.
Start jou views / rss.builder
bekijk het bestand met het volgende:
xml.instruct! : .xml,: version => "1.0" xml.rss: version => "2.0" do xml.channel do end end
Zeer belangrijke opmerking: Verwijder de punt (in de eerste seconde van het bovenstaande codeblok).
) in de tekst : .xml
. WordPress interfereert met codefragmenten.
Bouwer zal dit als volgt analyseren:
Dus we zijn begonnen met het maken van de structuur voor een geldig XML-bestand. Laten we nu tags toevoegen voor de feedtitel, beschrijving en een link terug naar de hoofdsite. Voeg het volgende toe in de xml.kanaal doen
blok:
xml.title "Recall" xml.description "omdat je het te druk hebt om te onthouden" xml.link request.url
Merk op hoe we de huidige URL van de krijgen verzoek
voorwerp. We zouden dit handmatig kunnen coderen, maar het idee is dat je de app overal kunt uploaden zonder dat je obscure stukjes code hoeft te veranderen.
Er is echter één probleem, de link is nu ingesteld op (bijvoorbeeld) http: // localhost: 9393 / rss.xml
. Idealiter willen we dat de link naar de startpagina gaat en niet terug naar de feed. De verzoek
object heeft ook een PATH_INFO
methode die is ingesteld op de huidige routestring; dus in ons geval, /rss.xml
.
Als we dit weten, kunnen we nu Ruby's gebruiken Chomp
methode om het pad van het einde van de URL te verwijderen. Verander de xml.link request.url
regel naar:
xml.link request.url.chomp request.path_info
De link in ons XML-bestand is nu ingesteld op http: // localhost: 9393
. We kunnen nu elke notitie doorlopen en er een nieuw XML-item voor maken:
@ notes.each do | note | xml.item do xml.title h note.content xml.link "# request.url.chomp request.path_info / # note.id" xml.guid "# request.url.chomp request.path_info / # note.id "xml.pubDate Time.parse (note.created_at.to_s) .rfc822 xml.description h note.content end end
Merk op dat we op regel 3 en 7 aan de inhoud van de notitie ontsnappen met behulp van h
, net zoals we deden in de belangrijkste weergaven. Het is een beetje vreemd om dezelfde inhoud weer te geven voor beide titel
en de Omschrijving
tags, maar we volgen Twitter's voorbeeld hier, en er zijn geen andere gegevens die we daar kunnen plaatsen.
Op regel 6 converteren we de notitie's gemaakt bij
tijd tot RFC822, het vereiste formaat voor tijden in RSS-feeds.
Probeer het nu in een browser! Ga naar /rss.xml
en je aantekeningen moeten correct worden weergegeven.
Er is een klein probleempje met onze implementatie. In onze RSS-weergave hebben we de titel en beschrijving van de site. We hebben ze ook in de views / layout.erb
bestand voor het grootste deel van de site. Maar als we nu de naam of beschrijving van de site willen wijzigen, zijn er twee verschillende plaatsen die we moeten bijwerken. Een betere oplossing zou zijn om de titel en beschrijving in te stellen een plaats, verwijs ze dan vanaf daar.
Binnen in de recall.rb
toepassingsbestand, voeg de volgende twee regels rechtstreeks toe aan de bovenkant van het bestand na de vereisen
statements, om twee constanten te definiëren:
SITE_TITLE = "Rappel" SITE_DESCRIPTION = "omdat je het te druk hebt om te onthouden"
Nu weer naar binnen views / rss.builder
verander regels 4 en 5 in:
xml.title SITE_TITLE xml.description SITE_DESCRIPTION
En van binnen views / layout.erb
verander de
tag op regel 5 naar:
<%= "#@title | #SITE_TITLE" %>
En verander de h1
en h2
title-tags op regels 12 en 13 naar:
<%= SITE_TITLE %>
<%= SITE_DESCRIPTION %>
We zouden ook een link naar de RSS-feed moeten opnemen in de hoofd
van de pagina, zodat browsers een RSS-knop in de adresbalk kunnen weergeven. Voeg het volgende direct vóór de label:
We hebben een manier nodig om de gebruiker te informeren wanneer er iets fout is gegaan - of juist, zoals een bevestigingsbericht wanneer een nieuwe notitie is toegevoegd, een notitie is verwijderd, enz..
De meest gebruikelijke en logische manier om dit te bereiken is door middel van "flashberichten" - een kort bericht toegevoegd aan de browsersessie van de gebruiker, dat wordt weergegeven en gewist op de volgende pagina die ze bekijken. En er zijn gewoon een paar RubyGems om dit te bereiken! Voer het volgende in de Terminal in om Rack Flash en Sinatra Redirect met Flash-edelstenen te installeren:
gem installeren rack-flash sinatra-redirect-with-flash
Afhankelijk van hoe u RubyGems heeft ingesteld op uw systeem, moet u mogelijk een voorvoegsel gebruiken gem installeren
met sudo
.
Vereis de edelstenen en activeer hun functionaliteit door het volgende toe te voegen aan de bovenkant van je recall.rb
toepassingsbestand:
vereisen 'rack-flash' vereisen 'sinatra / redirect_with_flash' inschakelen: sessies gebruiken Rack :: Flash,: sweep => true
Het toevoegen van een nieuw flashbericht is zo simpel als flash [: error] = "Er is iets verkeerd gegaan!"
. Laten we een fout weergeven op de startpagina als er geen notities in de database zijn.
Verander jouw krijg '/'
weg naar:
get '/' do @notes = Note.all: order =>: id.desc @title = 'Alle notities' als @ notes.empty? flash [: error] = 'Geen notities gevonden. Voeg je eerste hieronder toe. ' end erb: home-end
Erg makkelijk. Als het @notes
instantievariabele is leeg, maak een nieuwe flash-fout. Om deze flitsberichten op de pagina weer te geven, voegt u het volgende toe aan uw views / layout.erb
bestand, vóór de <%= yield %>
:
<% if flash[:notice] %><%= flash[:notice] %> <% end %> <% if flash[:error] %>
<%= flash[:error] %> <% end %>
En voeg de volgende stijlen toe aan uw public / style.css
bestand om berichten groen weer te geven en fouten in rood:
.merk op color: green; .error color: red;
Nu zou uw startpagina het bericht "geen opmerkingen gevonden" moeten weergeven als de database leeg is:
Laten we nu een fout- of succesbericht weergeven, afhankelijk van of een nieuwe notitie aan de database zou kunnen worden toegevoegd. Verander jouw post '/'
weg naar:
post '/' do n = Note.new n.content = params [: inhoud] n.created_at = Time.now n.updated_at = Time.now if n.save redirect '/',: notice => 'Notitie succesvol aangemaakt .' else redirect '/',: error => 'Notitie opslaan mislukt.' einde
De code is redelijk logisch. Als de notitie kan worden opgeslagen, wordt u omgeleid naar de startpagina met een 'kennisgeving'-flashbericht, of wordt u omgeleid naar een startpagina met een foutbericht. Hier ziet u de alternatieve syntaxis voor het instellen van een flash-bericht en het omleiden van de pagina aangeboden door de Sinatra-Redirect-With-Flash-juweel.
Het zou ook ideaal zijn om ook een foutmelding weer te geven op de pagina 'Notitie bewerken' als de gevraagde notitie niet bestaat. Verander de krijg '/: id'
weg naar:
get '/: id' do @note = Note.get params [: id] @title = "Bewerk noot ## params [: id]" als @note erb: bewerk else redirect '/',: error => "Ik kan die notitie niet vinden." einde
En ook op de pagina PUT-aanvraag voor het bijwerken van een notitie. Verandering zet '/: id'
naar:
zet '/: id' do n = Note.get params [: id] tenzij n redirect '/',: error => "Kan die notitie niet vinden." end n.content = params [: content] n.complete = params [: compleet]? 1: 0 n.updated_at = Time.now if n.save redirect '/',: notice => 'Notitie succesvol bijgewerkt.' else redirect '/',: error => 'Fout bij bijwerken notitie'. einde
Verander de krijg '/: id / delete'
weg naar:
get '/: id / delete' do @note = Note.get params [: id] @title = "Bevestig verwijdering van noot ## params [: id]" als @note erb: bewerk else redirect '/', : error => "Kan die notitie niet vinden." einde
En het bijbehorende VERWIJDEREN verzoek, verwijder '/: id'
naar:
delete '/: id' do n = Note.get params [: id] if n.destroy redirect '/',: notice => 'Opmerking is succesvol verwijderd.' else redirect '/',: error => 'Fout bij verwijderen van notitie'. einde
Wijzig tot slot de krijg '/: id / complete'
route naar het volgende:
get '/: id / complete' do n = Note.get params [: id] tenzij n redirect '/',: error => "Kan die notitie niet vinden." einde n.complete = n.complete? 0: 1 # flip it n.updated_at = Time.now if n.save redirect '/',: notice => 'Opmerking gemarkeerd als voltooid.' else redirect '/',: error => 'Fout bij het markeren van opmerking als voltooid.' einde
Een werkende, veilige en foutgevoelige web-app geschreven in een verrassend kleine hoeveelheid code! Over deze korte miniserie hebben we geleerd hoe verschillende HTTP-verzoeken met een RESTful-interface te verwerken, formulierinzendingen af te handelen, potentieel gevaarlijke inhoud te ontsnappen, verbinding te maken met een database, met gebruikerssessies te werken om flashberichten weer te geven, een dynamische RSS-feed te genereren en hoe u correct omgaan met toepassingsfouten.
Als u de app verder wilt gebruiken, wilt u misschien kijken naar het omgaan met gebruikersauthenticatie, zoals met het juweeltje Sinatra Authentication.
Als je de app op een webserver wilt implementeren, omdat Sinatra is gebouwd met Rake, kun je heel gemakkelijk je Sinatra-applicaties hosten op Apache- en Nginx-servers door Passenger te installeren.
Of kijk eens naar Heroku, een Git-powered hostingplatform waarmee u uw Ruby-webapps eenvoudig kunt implementeren git push heroku
(gratis accounts zijn beschikbaar!)
Als je meer wilt weten over Sinatra, bekijk dan het zeer uitgebreide Leesmij-bestand, de documentatiepagina's en het gratis Sinatra-boek.
Notitie: de bronbestanden voor elk deel van deze miniserie zijn beschikbaar op GitHub, samen met de voltooide app.