| Rails ETags and Authentication | [September 14, 2011 | 4:22 PM] |
|---|
ETags are great for keeping the expensive stuff from executing over and over again, for example:
1 2 3 4 5 |
def some_action if stale?(:etag => some_obj, :last_modified => some_obj.created_at, :public => true) some_obj.some_really_expensive_operation end end |
Will prevents some_really_expensive_operation from executing a second time when the user revisits the page if nothing in the object has changed. This works great on public pages but what if you have some expensive stuff on a page that requires authentication, or a page that the content changes when a user logs in? Fortunately, any object can be passed for the :etag option. We could create a method called authenticated_stale? and include the current user as follows in the application controller as follows:
1 2 3 4 5 6 7 8 9 10 11 12 |
class ApplicationController < ActionController::Base #your code def current_user @current_user ||= User.find_by_id(session[:user_id] end def authenticated_stale?(options = {}) options[:etag] = [options[:etag], current_user] stale?(options) end end |
And use authenticated_stale? for any page that requires authentication or where the content changes if you are authenticated as you would the stale? method. Make sure not to set the public option true on authenticated pages so they don’t get cached by proxy caches.
| Custom Validators Done With I18n Localization | [August 19, 2011 | 9:27 AM] |
|---|
Rails three has made it very easy to write custom validators. Ryan Bates has a beautiful example of how to use these validators here (make sure to watch this or read the ascii cast). He uses the following example to illustrate how to create a custom validator for checking the format of an email address:
1 2 3 4 5 6 7 |
class EmailFormatValidator < ActiveModel::EachValidator def validate_each(object, attribute, value) unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i object.errors[attribute] << (options[:message] || "is not formatted properly") end end end |
This does a good job but is not very useful for an international site. The above code can be rewritten as follows:
1 2 3 4 5 6 7 |
class EmailFormatValidator < ActiveModel::EachValidator def validate_each(object, attribute, value) unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i object.errors.add(attribute, (options[:message] || :email_format)) end end end |
Essentially, we replace the string ‘is not formatted properly’ with the symbol :email_format. This symbol matches the type of validation we are performing.
In addition to this, add the following to the en.yml file of your rails app:
1 2 3 4 |
en: errors: messages: email_format: "is not formatted properly" |
This way you can customize translations not only for the built in rails validators but also custom ones you define yourself.
Feel free to use the code above subject to the following license
| Workaround for Connecting to VMWare Guest While Using Cisco VPNClient | [January 30, 2011 | 12:10 PM] |
|---|
Currently I’m working on a project for a company which requires me to be on a VPN in order to access their websites and other resources. I use Cisco VPNClient in order to connect to the IPSec VPN they have. The project requires me to develop an application using Oracle. I am normally a Mac user and Oracle does not supply an OSX version of Oracle XE. My initial solution was to install Oracle XE on an Ubuntu virtual machine which I could than connect to with my Mac. Things were working correctly at first. I pointed my Rails application to the Ubuntu VM’s ip address and was able to connect to the database and run migrations. As soon as I connected to the VPNClient, I was unable to connect to the database using my Mac or network with the Ubuntu VM in anyway. The Ubuntu machine could connect to the internet but any ping I did to it from my Mac timed out.
Upon further research, I found out that VPNClient changes the way networking traffic is routed. Essentially all traffic gets routed to the VPN server that the VPNClient is connected to (I already new this but figured the client would ignore requests to the local network). Knowing that the Ubuntu VM was on a virtual local network I figured enabling ‘Allow Local LAN Access’ would do the trick (you may want to try this solution before taking the steps below). This unfortunately did not solve the problem. Although this option exists, the administrators of the VPN Server can choose to disable this for all users. I tried contacting the administrators and asking if they could enable local networking for me. I was told that it would be impossible to do this without enabling it for everyone and could be a security vulnerability.
I tried changing the routing tables but this too, did not work for me (I might have done it wrong, if this worked for anybody let me know). I also tried connecting to the native Snow Leopard Cisco IPSec VPN. This allowed me to connect to the VPN and connect to my virtual machines but the internet stopped working (I think some of the settings that can be enabled in VPNClient are missing in the Snow Leopard version such as transparent tunneling and using IPSec over UDP).
After reading this article on port forwarding using VMWare Fusion, I wondered if it would be possible to use with the VPN (after all, I’m routing to the localhost, not an external machine). I opened up textmate and opened up the file at:
1 |
/Library/Application Support/VMware Fusion/vmnet8/nat.conf |
I added the following lines:
1 2 3 4 5 6 |
[incomingtcp] #192.168.10.3 is the ip I use for my VM #Oracle Web Admin Page 8080 = 192.168.10.3:8080 #Default Oracle Port 1521 = 192.168.10.3:1521 |
ran the command:
1 |
sudo "/Library/Application Support/VMware Fusion/boot.sh" --restart |
I opened up a brower on my Mac and typed in ‘http://localhost:8080/apex’ and was presented with the Oracle XE web interface login page. I than connected to the VPN and did the same thing to make sure that it would work over the VPN. After being presented with the login screen a second time I rejoiced a bit, and promptly tried connecting directly to the database with my rails app (had to modify the url of course).
This ended up being a good enough workaround to keep me developing in OSX instead of having to resort to using Windows or Ubuntu for this one job. There are probably better solutions out there which will allow VPNClient to recognize the virtual machines as safe (all the traffic from the VMs also get routed through the VPN if using a NAT connection, I’m not sure about bridged though). The irritating part of this workaround is that you have to specify each port you wish to use and make sure that you set static IPs that match the ports specified in the nat.conf file. If anybody has any better solutions, please respond in the comments section.
| php import_request_variables exploit example | [December 31, 2009 | 5:41 PM] |
|---|
I previously ran across some code that used import_request_variables to set all post, get and cookie parameters to global variable. This saved time since you would not need to explicitly call:
1 2 3 |
if(isset($_GET['some_param']) and $_GET['some_param']){
$some_var = $_GET['some_param'];
}
|
It was called using:
1 |
import_request_variables('GPC','_') ;
|
This code was called on every page of the site. The first parameter ‘GPC’ tells import_request_variables to import get, post and cookie parameters. The second parameter ‘_’ tells import_request_variables to add a underscore to the variable name. For example, passing $_GET[‘foo’] will create a variable called $_foo. If the second parameter is passed, there will be a E_NOTICE warning since this would allow any user to create or modify any variable. In this code, the underscore was provided as a prefix but this does not mean we are in the clear security wise. Before I show the example, lets draw up some example code for index.php.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<?
#the following code is example code
#and should not be used as an example
#of the "right" way of doing things.
#There may even be syntax errors.
#starts the session, connects to db,
#and calls import_request_variables
function begin(){
session_start();
mysql_connect('localhost',
'mysql_user',
'mysql_password');
import_request_variables('GPC', '_');
}
#checks username and password with db
#and sets $_SESSION['user_id'] if they authenticate successfully.
function login($username, $password){
$username = mysql_real_escape_string($username);
$password = mysql_real_escape_string($password);
#as stated above, this code is only an example,
#for a real site you would want a hash/salt, not plaintext
#password.
$query = "SELECT * FROM users WHERE username =
'$username' and password = '$password' LIMIT 1";
$result = mysql_query($query);
$user = mysql_fetch_assoc($result);
if($user){
$_SESSION['user_id'] = $user['user_id'];
}
}
#destroys session
function logout(){
session_destroy();
}
#will return true if $_SESSION['user_id'] is set and belongs to a user
function isLoggedIn(){
if(isset($_SESSION['user_id'])){
$user_id = $_SESSION['user_id'];
$query = "SELECT * FROM users WHERE user_id = '$user_id' LIMIT 1";
$result = mysql_query($query);
$user = mysql_fetch_assoc($query);
if($user){
return true;
}else{
return false;
}
}else{
return false;
}
}
begin();
#note that variables starting with
#$_ where set above with import_request_variables
if(isset($_logout)){
logout();
}
if(isset($_username) && isset($_password)){
login($_username, $_password);
}
if(isLoggedIn()){
?>
You are an authorized user! <a href='index.php?logout=true'>Logout</a>.
<?
}else{
?>
You are not authorized, use the form below to log in.
<form action='index.php'>
Username: <input type='text' name='username' /><br />
Password: <input type='password' name='password' />
</form>
<?
}
?>
|
This is a simple page which will tell you if you are authorized, or give the login form if not. If the user successfully logs in, the $_SESSION[‘user_id’] is set. $_username and $_password are set by import_request_variables if the username and password get, post or cookie variables are set. Now on to the attack. Any variable starting with $_ can be set by passing a get, post or cookie with the appropriate parameters. If a user sent the request http://www.example.com/?SESSION[user_id]=1 would overwrite the $_SESSION[‘user_id’] variable to 1. Since database ids are often sequential, it would not be that hard to use a script to run through each number and find ids that belong to users. It would essentially allow a hacker to log in as any user. Make sure if using the import_request_variables function, make sure to choose a prefix that will not be used by any other variables.
| Scrolling Element | [December 20, 2009 | 9:45 PM] |
|---|
So they other day I was thought to mysel: “why don’t I add one of those annoying scrolling advertising thingies at the side of my page so that all my users are forced to see adds wherever they are on my page?” Than I realized, those things are really annoying. It’s not so much that they follow you wherever they scroll, it’s more that they don’t scroll really smoothly (or at least that’s why I hate them). Most of these scrolling ads use javascript to observe when you scroll and change the position. Why not just use the css position attribute and set it to fixed. Fixed makes the div position relative to the browser window, and not any other elements. I added the following css.
1 2 3 4 5 |
#ads{ position: fixed; top: 10px; left: 1100px; } |
This worked out really well, the ads were right where they should be. When I scrolled, the element moved with the window smoothly instead jittering about while scrolling. Everything was great … until I resized the window.
When I made the window smaller, the ad div would move away from the content div. When I made the window bigger, the ad div would overlap the content div (though that might increase ad revenue by making people click on it by accident. What I really wanted was for the top attribute to be fixed to the browser window and the left attribute absolute to the content. Unfortunately css only has the position property which effects both top and bottom (if I’m wrong, please let me know).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var ScrollingElm = Class.create({ initialize: function(target_div, parent_div, options){ this.target = $(target_div); this.parent = $(parent_div); this.handleOptions(options); this.setStyle(); }, handleOptions: function(options){ this.options = Object.extend({ top: 20, left: 800 }, options || {}); }, setStyle: function(){ leftOffset = this.parent.positionedOffset()[0] + parseInt(this.options.left); leftPos = leftOffset + "px"; topPos = this.options.top + "px"; this.target.setStyle({ position: 'fixed', left: leftPos, top: topPos }); this.target.show() }, }); |
Essentially I pass a target element, which is the element we want to adjust the position to, and the parent element which we want to use as the anchor for the left attribute. The default values I set to top 20 and left 800, I figured they would work well for many website. These can be overridden by passing the appropriate options. The top attribute is relative to the browser since the position is set to fixed. Technically left is also relative to the browser window, but the offset is dynamically calculated with prototypes Element.positionedOffset method. You may think that I would be able to use Prototypes Event.observe to detect when the window is resized. Unfortunately, the resize event seems to not be supported by prototype. I got around this by creating a ScrollingElm object and than manually setting the onresize attribute in the body tag.
1 |
var scroller = new ScrollingElm('ads', 'container', {top: '10', left: '750'}); |
1 2 3 4 5 6 |
<body onresize="scroller.setStyle()"> <div id='ads' style='display: none;'> add content </div> ... content </body> |
Unfortunately this relies on creating a global variable named scroller but it gets the job done. I may explore further why Prototype does not support the resize event but this works for now. I set display to none because the javascript will show the element and this way the ad div will not mess up the layout before the js loads. I’ve tested this script in Firefox 3.5.6, Chrome 4.0.249.43, and Internet Explorer 7. Let me know if you find a browser it does not work with and I may look into it.
| Enki Setup | [April 11, 2009 | 12:31 PM] |
|---|
So I finally broke down and started my own blog. Before I used services such as Livejournal and SmugMug to share photos and my life story. For awhile now I’ve been been wanting to start a tech blog and figured Livejournal no longer met my needs. I hope to start to develop Ruby on Rails plugins and host them on this site to share them with the Rails community. For now I’m going to give a short tutorial on setting up an Enki blog.
I decided to use Enki since it is Ruby on Rails based and I happen to be a Ruby on Rails developer. From what I’ve read it seemed much more light weight than other blogs. Customization is done through hacking the code. By default Enki is not very pretty, it just gives you the skeleton needed to contain posts, pages and other standard blog stuff. To change this, just hack at the rhtml files and css files to taste. Remember Enki is just a rails app which has the skeleton of a good blog.
I have modified the instructions at Enki website. Essentially my instructions are the same but try to fill in some of the gaps in the instructions. It is assumed that whoever attempts this has knowledge of Ruby on Rails.
1 2 3 |
git clone git://github.com/xaviershay/enki.git enki git checkout -b myblog # Create a new work branch cp config/database.example.yml config/database.yml |
At this point you want to edit database.yml appropriately. You also want to edit config/enki.yml. By default it contains the following parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# Configuration options for your blog - customise to taste # This file contains no secret information, so can be stored in source control (unlike database.yml) title: My Enki Blog url: http://enkiblog.com author: name: Don Alias # For copyright notice and ATOM feeds email: don@enkiblog.com # Exception emails will go here, and it is used in ATOM feeds open_id: # These are used to login to the admin area - http://enkiblog.com - http://secondaryopenid.com # Delete the following section if your site will not be acting as an OpenID delegate (http://wiki.openid.net/Delegation) # If you're deploying with mongrel, make sure you read http://rhnh.net/2008/04/13/nginx-openid-delegation-and-yadis open_id_delegation: server: http://www.myopenid.com/server delegate: http://username.myopenid.com |
You probably wanna change the title, url and author info. Under open_id you will notice a list of open id urls. You should delete these and add your own open id url. I got my openid from myopenid. If you are unfamiliar with open id authentication, Ryan Bates has an excelent railscast on the subject. You need to have an openid in order to login with the default Enki login. If you want to have multiple users login to your blog, you can probably add multiple open ids to the list but you are probably better off storing users in a database and keeping there open id info stored there.
1 2 3 |
rake db:migrate rake spec ./script/server |
Note, after running rake spec, it will notify you on what other plugins you need to install. You can probably just run
1 |
rake gems:install |
to install the required gems. At this point your blog should be ready to go. You may notice if you go to localhost:3000/admin there is a checkbox that has Bypass credentials check. This allows you to log in without any credentials for development. This checkbox, and the ability to bypass security are disabled in production mode.
Obviously at this point you will want to customize the blog to your needs but you should have the skeleton needed to get started.
Happy Blogging,
Thijs