contrib: add shibuqam to extract information from a shibboleth authentication
authorJean Privat <jean@pryen.org>
Thu, 11 Aug 2016 19:42:04 +0000 (15:42 -0400)
committerJean Privat <jean@pryen.org>
Thu, 11 Aug 2016 19:42:04 +0000 (15:42 -0400)
Signed-off-by: Jean Privat <jean@pryen.org>

contrib/shibuqam/examples/reloadgame.nit [new file with mode: 0644]
contrib/shibuqam/package.ini [new file with mode: 0644]
contrib/shibuqam/shibuqam.nit [new file with mode: 0644]

diff --git a/contrib/shibuqam/examples/reloadgame.nit b/contrib/shibuqam/examples/reloadgame.nit
new file mode 100644 (file)
index 0000000..24eecf5
--- /dev/null
@@ -0,0 +1,115 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example that uses `shibuqam` to authenticate users and count the number of time they reload.
+module reloadgame
+
+import popcorn
+import counter
+import shibuqam
+
+redef class User
+       # How many reload?
+       var seen = 0
+end
+
+# Ugly global class to track the knowledge.
+class DB
+       # All known users
+       var users = new HashMap[String, User]
+end
+# Ugly global instance to track the knowledge.
+fun db: DB do return once new DB
+
+redef class HttpRequest
+       # Like `user` but reuse an user if already seen
+       var reuser: nullable User is lazy do
+               var user = self.user
+               if user == null then return null
+
+               var saved = db.users.get_or_null(user.id)
+               if saved != null then return saved
+
+               db.users[user.id] = user
+               return user
+       end
+end
+
+# The only handler of the example.
+class ReloadGame
+       super Handler
+
+       redef fun get(http_request, response)
+       do
+               var body = """
+                       <!DOCTYPE html>
+                       <head>
+                       <meta charset="utf-8">
+                       <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+                       <title>Nitcorn on Shibboleth/UQAM</title>
+                       </head>
+                       <body>
+                       <div class="container">
+                       <h1>Nitcorn on Shibboleth/UQAM</h1>
+               """
+
+               var user = http_request.user
+
+               if user != null then
+                       user.seen += 1
+
+                       body += """
+                       <p>Welcome {{{user.given_name}}}</p>
+                       <ul>
+                       <li>Full Name: {{{user.display_name}}}</li>
+                       <li>E-Mail: {{{user.email}}}</li>
+                       <li>Id: {{{user.id}}}</li>
+                       <li>Score: {{{user.seen}}}</li>
+                       </ul>
+                       """
+
+                       #for k, v in http_request.header do body += "<li>{k}: {v}</li>"
+               else
+                       # The login page, at the location the reverse proxy is expected to be configured
+                       # to force an authentication.
+                       var login = "/securep/login"
+                       body += """
+                       <p>Welcome annonymous, please <a href="{{{login}}}">log in</a>.</p>
+                       """
+               end
+
+               var score = new Counter[User]
+               for u in db.users.values do
+                       score[u] = u.seen
+               end
+
+               body += "<h2>Scoreboard</h2><ul>"
+               for u in score.sort.reversed do
+                       body += "<li><img src='{u.avatar}'> {u.display_name}: {u.seen}</li>"
+               end
+
+
+               body += """</ul>
+                       </div>
+                       </body>
+                       </html>
+               """
+
+               response.html body
+       end
+end
+
+var app = new App
+app.use("/*", new ReloadGame)
+app.listen("localhost", 3000)
diff --git a/contrib/shibuqam/package.ini b/contrib/shibuqam/package.ini
new file mode 100644 (file)
index 0000000..8c619ad
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=shibuqam
+tags=web
+maintainer=Jean Privat <jean@pryen.org>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/contrib/shibuqam/
+git=https://github.com/nitlang/nit.git
+git.directory=contrib/shibuqam/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
diff --git a/contrib/shibuqam/shibuqam.nit b/contrib/shibuqam/shibuqam.nit
new file mode 100644 (file)
index 0000000..e8da9a5
--- /dev/null
@@ -0,0 +1,69 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Gather the authenticated users on UQAM websites.
+#
+# The main method to use is `HttpRequest::user` and it extracts the information
+# of the authenticated user from the request header.
+# The real authentication must be done by a mandatory reverse proxy server.
+module shibuqam
+
+import nitcorn
+private import md5
+
+# Information on a user from Shibboleth/UQAM
+class User
+       # The *code permanent* (or the uid for non student)
+       var id: String
+
+       # Usually the first name
+       var given_name: String
+
+       # Usually "FamilyName, FirstName"
+       var display_name: String
+
+       # The email @courrier.uqam.ca (or @uqam.ca for non student)
+       var email: String
+
+       # The Gravatar URL (based on `email`)
+       var avatar: String is lazy do
+               var md5 = email.md5
+               return "https://www.gravatar.com/avatar/{md5}?d=retro"
+       end
+end
+
+redef class HttpRequest
+       # Extract the Shibboleth/UQAM information from the header, if any.
+       #
+       # We assume that a reverse proxy does the authentication and fill the request header.
+       # If the server is accessible directly, these headers can be easily forged.
+       # Thus, we assume that the reverse proxy is not by-passable.
+       #
+       # The reverse proxy might choose to force an authentication or not.
+       # If there is no authentication, there is no information in the request header (or with the `(null)` value).
+       # In this case, `null` is returned by this function.
+       fun user: nullable User do
+               var user = header.get_or_null("Remote-User")
+               if user == null or user == "(null)" then return null
+
+               var display_name = header.get_or_null("User-Display-Name")
+               var given_name = header.get_or_null("User-Given-Name")
+               var email = header.get_or_null("User-Mail")
+
+               if display_name == null or given_name == null or email == null then return null
+
+               var res = new User(user, given_name, display_name, email)
+               return res
+       end
+end