Zingen met Sinatra - The Encore

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.


Gebruikers zijn slecht, m'kay

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:

XSS op de andere pagina's

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).


RSS Feed the Masses

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.


DRY Do not Repeat Yourself

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 </code> tag op regel 5 naar:</p> <pre> <title><%= "#@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:

 

Flash-berichten, fouten en successen

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

En daar heb je het!

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.