I've implemented a more secure cookie-based auto-login in TF-Login 'password' branch to replace the existing simple and insecure cookie scheme.
To load, start with fresh Pharo 7 image:
"First load Seaside."
Metacello new
baseline: 'Seaside3';
repository: 'github://SeasideSt/Seaside:v3.3.3/repository';
load.
"Then load TF-Login."
Metacello new
baseline: 'TFLogin';
repository: 'github://PierceNg/TF-Login:password/src';
load.
Start Test Runner to run TF-Login's tests. All 78 tests should pass. The class
TLTest
runs "scripted interactive" tests on the TLTester
Seaside
application. TLTest
's testLoginLogout
and testLoginThenAutomaticLogin
methods exercise the cookie-based auto-login functionality.
Auto-login is also implemented in the TLTestApp
demo Seaside application. Here's a
screenshot of the cookie stored in Chromium upon logging into TLTestApp
.
The original cookie-based auto-login stores username and the SHA1-hashed password in client cookies. This replacement implementation is based on Paragon Initiative's blog post on "remember me" cookies.
Tags: Seaside, security, TF-LoginI've implemented PBKDF2-HMAC-SHA1 in TF-Login 'password' branch to replace the existing simple and insecure SHA1-based password hashing scheme.
To load, start with fresh Pharo 7 image:
"First load Seaside."
Metacello new
baseline: 'Seaside3';
repository: 'github://SeasideSt/Seaside:v3.3.3/repository';
load.
"Then load TF-Login."
Metacello new
baseline: 'TFLogin';
repository: 'github://PierceNg/TF-Login:password/src';
load.
As originally implemented, TF-Login also supports cookie-based auto-login, which works by storing username and the SHA1-hashed password in client cookies. This scheme is certainly not secure by current standards and can't be used together with PBKDF2-HMAC-SHA1 password hashing.
Possible future work on TF-Login password management:
OAuth2, to replace the existing insecure cookie-based auto-login
2FA
I've ported TF-Login to Seaside 3.3 and Pharo 7.
To load, start with fresh Pharo 7 image:
Metacello new
baseline: 'Seaside3';
repository: 'github://SeasideSt/Seaside:v3.3.3/repository';
load.
Metacello new
baseline: 'TFLogin';
repository: 'github://PierceNg/TF-Login:pharo7/src';
load.
The baseline doesn't load Seaside, in case you want to load it into an image that already has Seaside.
Start Test Runner and run the TF-Login tests. All 62 tests should pass.
Tags: Seaside, security, TF-LoginTF-Login is a package that provides basic user authentication, registration, and account management for Seaside. It was originally developed on Pharo 1 by Tony Fleig, and ported to Pharo 2 by sergio101.
I am attempting to make TF-Login work with Seaside 3.3.x on Pharo 7. For now, I've reorganized the packages and fixed TLMultiFileDatabase to pass its tests.
Collaborators are welcome.
Tags: Seaside, security, TF-LoginI've implemented RESTful APIs for RedditSt20 using Seaside-REST. The API endpoints are accessible using Curl:
% curl -X GET -H "Accept: text/json" http://127.0.0.1:8080/redditst20-api/latestLinks
<JSON output in one long line>
The same from Zinc, using NeoJSON to pretty-print:
NeoJSONWriter toStringPretty:
(NeoJSONObject fromString:
(ZnEasy get: 'http://127.0.0.1:8080/redditst20-api/latestLinks') contents)
Output:
[
{
"points" : 0,
"url" : "http://postgresql.org",
"created" : "2018-01-13T20:03:56.405254+08:00",
"title" : "PostgreSQL",
"id" : 9
},
{
"points" : 0,
"url" : "http://sqlite.org",
"created" : "2018-01-13T20:03:56.404526+08:00",
"title" : "SQLite",
"id" : 10
},
{
"points" : 0,
"url" : "http://pharo.org",
"created" : "2018-01-13T20:03:56.403656+08:00",
"title" : "Pharo",
"id" : 11
}
]
Voting is implemented using POST:
% curl -X POST \
-H "Accept: text/json" \
-H "Content-Type: text/json" \
http://127.0.0.1:8080/redditst20-api/11/voteUp
{"Updated":"true"}
%
Let's check out the highest ranking links:
NeoJSONWriter toStringPretty:
(NeoJSONObject fromString:
(ZnEasy get: 'http://127.0.0.1:8080/redditst20-api/highestRankingLinks') contents)
Here's the output:
[
{
"points" : 1,
"url" : "http://pharo.org",
"created" : "2018-01-13T20:03:56.403656+08:00",
"title" : "Pharo",
"id" : 11
},
{
"points" : 0,
"url" : "http://postgresql.org",
"created" : "2018-01-13T20:03:56.405254+08:00",
"title" : "PostgreSQL",
"id" : 9
},
{
"points" : 0,
"url" : "http://sqlite.org",
"created" : "2018-01-13T20:03:56.404526+08:00",
"title" : "SQLite",
"id" : 10
}
]
To create a new link, use PUT:
% curl -X PUT \
-H "Accept: text/json" \
-H "Content-Type: text/json" \
-d '{"title":"Glorp", "url":"http://glorp.org"}' \
http://127.0.0.1:8080/redditst20-api/putLink
{"Created":true}
%
And here it is:
[
{
"points" : 0,
"url" : "http://glorp.org",
"created" : "2018-01-13T20:15:15.569197+08:00",
"title" : "Glorp",
"id" : 12
},
...
]
Code is on STH. I plan to move it to GH anytime now.
Tags: SeasideI have started a booklet on Pharo, hopefully the first of, um, more than one. It is entitled RedditSt20, on my fork and extension of Sven Van Caekenberghe's excellent "Reddit.st in 10 elegant classes", to cover the following in another 10 or so classes:
The book is hosted on Github. Source code is on Smalltalkhub.
The book is being written using Pillar, of course. Note that the Pharo 5
version of Pillar that I downloaded from InriaCI doesn't work - the
supporting makefiles aren't able to obtain the output of "./pillar
introspect
I just tried loading Seaside into the latest Pharo 6 pre-release image from the Catalog Browser. It loads cleanly. However, the Seaside control panel doesn't start because it uses NewListModel which does not exist in this image:
WAPharoServerAdapterSpecBrowser>>#initializeWidgets
self instantiateModels: #(
listModel NewListModel "<== Exists In Pharo 5, not in Pharo 6"
textModel TextModel
toolbarModel WAServerAdapterToolbar).
Ok, how about Pharo 5? Using Pharo 50772, which is currently the latest Pharo 5 image, I loaded Seaside 3.1.5 programmatically after visually inspecting ConfigurationOfSeaside3:
Gofer it
smalltalkhubUser: 'Seaside' project: 'Seaside31';
package: 'ConfigurationOfSeaside3';
load.
(#ConfigurationOfSeaside3 asClass project version: '3.1.5') load.
The load sequence includes messages to String>>#subStrings: which is deprecated. The senders are GRStringTest>>#testSubStrings, JQAjax>>#callback:passengers:, WAAdmin class>>#register:at:in: and WAAdmin class>>#unregister:in:.
Otherwise Seaside 3.1.5 loads cleanly. Test Runner reports 1173 run, 1171 passes and 2 expected failures.
Tags: SeasideHere's how to create a Seaside wrapper using, in this case, the Rainbow Text jQuery plugin as the example.
0 - Download the source into, say, '/home/pierce/src/jq/Rainbow-Text'.
1 - Create a file library.
WAFileLibrary subclass: #JQRainbowTextDevelopmentLibrary
instanceVariableNames: ''
classVariableNames: ''
category: 'PN-JQuery-UI'
2 - Load the Javascript (and CSS, if any) into the file library:
JQRainbowTextDevelopmentLibrary addFileAt: '/home/pierce/src/jq/Rainbow-Text/rainbow.js'.
3 - Subclass JQWidget.
JQWidget subclass: #JQRainbowText
instanceVariableNames: ''
classVariableNames: ''
category: 'PN-JQuery-UI'
4 - Add instance methods.
method
^ 'rainbow'
initialize
super initialize.
self animate: true;
animateInterval: 100;
colors: ('#ff0000' '#f26522' '#fff200' '#00a651' '#28abe2' '#2e3192' '#6868ff')
animate: aBooleanOrStringOrNumber
self optionAt: 'animate' put: aBooleanOrStringOrNumber
animateInterval: aNumber
self optionAt: 'animateInterval' put: aNumber
colors: anArray
self optionAt: 'colors' put: anArray
5 - Create the example component.
WAComponent subclass: #JQRainbowTextExample
instanceVariableNames: ''
classVariableNames: ''
category: 'PN-JQuery-UI'
6 - Add class-side methods.
canBeRoot
^ true
description
^ 'jQuery Rainbow Text'
initialize
| app |
app := WAAdmin register: self asApplicationAt: '/jqrainbowtext'.
app addLibrary: JQDevelopmentLibrary;
addLibrary: JQRainbowTextDevelopmentLibrary.
7 - Add instance methods.
updateRoot: aRoot
super updateRoot: aRoot.
aRoot script url: (JQRainbowTextDevelopmentLibrary urlOf: #rainbowJs).
renderContentOn: html
| id |
id := html nextId.
html div id: id; with: 'Because Rainbow Text Rules!'.
html script: (html jQuery id: id) rainbowText
8 - In a workspace, initialize JQRainbowTextExample.
JQRainbowTextExample initialize
9 - Create a method in JQueryInstance.
rainbowText
^ self create: JQRainbowText
10 - In the browser, visit 127.0.0.1:8080/jqrainbowtext.
The above is the standalone way. Alternatively, preferably, integrate the plugin into JQueryWidgetBox.
Tags: JQuery, SeasideAs I develop NBSQLite3 (tag, repository), I've been surfing the web for material on using Smalltalk with SQL databases effectively. This article, A Pattern Language for Relational Databases and Smalltalk by Kyle Brown and Bruce Whitenack, looks like it is from the late 90s but remains an interesting read.
The Seaside book is a bit light on persistency as is. The Hasso-Platter-Institut Seaside tutorial does present an abstract database wrapper class with these words:
In order to decouple your application from the chosen database system, it is a wise decision to encapsulate the required functionality within a separate database wrapper class. This class provides an interface for the required persistence functionality of the application but leaves the concrete implementation of those functions to its subclasses.
Tags: patterns, SeasideThis blog began life as a set of static pages, generated by a home-grown content management system written in Smalltalk, imaginatively called SmallCMS1.
I've now rewritten SmallCMS1 to serve content dynamically, to support tag linking, like this: SQLite.
Each blog post page now has forward and backward navigational links just above the blog post title.
Rendering code now uses Seaside. More than a year ago, I blogged on that. Seaside now has a cleaner way to render static HTML, or maybe that previous blog post got it wrong. Anyhow, here's how SmallCMS1 uses Seaside's HTML rendering engine:
^ WAHtmlCanvas builder
fullDocument: true;
rootBlock: [ :root | self renderSiteRootOn: root ];
render: [ :html | self renderContentOn: html ].
Similarly, RSS is rendered thusly:
^ RRRssRenderCanvas builder
fullDocument: true;
render: [ :rss | self renderRssOn: rss ]).
Tags: content management, RSS, Seaside
I've had this for a while. Putting it up here in case this is helpful to others. To use, create "htdocs" in "FileDirectory default". Web-wise, "htdocs" is known as "/static". I serve my CSS and JS files from there.
WAComancheAdaptor subclass: #WAComancheStaticAdaptor
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Seaside-Adaptors-Comanche'
WAComancheStaticAdaptor>>createService
| contentPath dirPath svc ma |
contentPath := 'htdocs'.
dirPath := FileDirectory default fullNameFor: contentPath.
svc := (HttpService on: self port)
name: 'seaside-' , self port greaseString.
ma := ModuleAssembly core.
ma alias: '/static' to: [
ma serverRoot: dirPath.
ma documentRoot: dirPath.
ma directoryIndex: 'index.html index.htm'.
ma serveFiles ].
ma addPlug: self.
svc plug: ma rootModule.
^ svc
Tags: Comanche, content management, Seaside
Back to Seaside, it also provides an API to generate RSS. Indeed, that is how this blog's feed is generated:
| doc ctx root rss |
String streamContents: [ :stream |
doc := WAXmlDocument new initializeWithStream: stream codec: nil.
ctx := WARenderContext new document: doc.
root := RRRssRoot new openOn: doc.
rss title: self siteTitle.
rss description: self siteSlogan.
self blog do: [ :ea |
rss item: [
rss title: ea title.
rss author: self siteAuthor.
rss link: self baseUrl, ea blogUrlPath.
rss guid: self baseUrl, ea blogUrlPath.
rss publicationDate: ea timestamp printHttpFormat.
rss description: ea outputHtml ]].
root closeOn: doc ]
Tags: RSS, Seaside
One of Seaside's distinctive features is that it generates HTML in Smalltalk, i.e., Seaside provides an API to produce HTML using Smalltalk code. This API can be used for generation of static web pages too:
| doc ctx root html |
String streamContents: [ :stream |
doc := WAHtmlDocument on: stream.
ctx := WARenderContext new document: doc.
root := WAHtmlRoot new.
root beXhtml11.
root title: 'Static HTML Generation using Seaside'.
root writeHeadOn: doc.
html := WARenderCanvas new initializeWithContext: ctx.
html html: '<p>Seaside!</p>'.
root writeFootOn: doc ]
Tags: Seaside