14
Apr 12

Prevent Mass Assignment Exploits in CakePHP

Github recently made headlines when it become a victim of the Ruby on Rails mass assignment vulnerability. This vulnerability isn’t just in Rails, it can be exploited in CakePHP as well. I will explain how this is possible as well as the very easy way to prevent it.

Say you have a User model, and a form to create a user like so:

<form id="UserRegisterForm" method="post" action="/users/register">
  <input name="data[User][email]" type="text" id="UserEmail" />
  <input name="data[User][password]" type="text" id="UserPassword" />
  <input name="data[User][phone]" type="text" id="UserPhone" />
</form>

*This isn’t meant to display a full registration form, just example to illustrate my point.

Then you have a register action on your user controller that looks like this:

public function register()
{
    if (!empty($this->data) && $this->User->save($this->data))
    {
        $this->Auth->login($this->data);
        $this->Session->setFlash('Your Account Has Been Created.');
        $this->redirect("/");
    }
}

Now image your User model has a property called admin that can be set to 1 or 0. All someone would have to do is edit the form to include an admin field and they could set themselves as an admin. An easy way would be to inspect a current text field and change the name from something like [User][phone] to [User][admin] and set the value to 1. When they submit the form, since the register action doesn’t know what fields to expect from the form, it will happily save them as an admin.

To prevent this all you need to do is specify what attributes are allowed to be set when saving:

$allowed_attributes = array('email','password','phone');
$this->User->save($this->data, true, $allowed_attributes);

Now any values passed that are not set in the $allowed_attributes array (like admin) will be ignored.


30
Mar 12

RVM and Gemsets Tutorial

The Ruby language uses “gems” to extend itself, Rails for example, is a gem. You may need to have different gems installed for different Ruby projects, you may even need different versions of Ruby; you can handle this easily using RVM. RVM stands for Ruby enVironment Manager, instructions for installing it can be found on RVM’s website.

Once you have RVM installed you can install a version of ruby using rvm install <version-number> for example, to install Ruby version 1.9.2 you would run:

rvm install 1.9.2

You can then list all the versions of Ruby in RVM with the command  rvm list. You can also use rvm to manage sets of gems, you can create a gemset with a command like rvm gemset create <gemset-name> for example:

rvm gemset create my-first-gemset

You can then tell rvm which version of ruby to use and which gemset to use like so:

rvm use 1.9.2@my-first-gemset

You really don’t want to have to run this command every time you switch between projects, so you can use .rvmrc files to switch your environment. You can put an .rvmrc file containing a command like above inside a directory and every time you change to that directory your rvm environment will change to it. You can put other commands to be run in your .rvmrc file as well, because of this, every time an edit is made to your .rvmrc file, you will be prompted before the commands are run.

Once you have your gemset selected you can install gems and they will only be installed in that particular gemset. You can intall gems like gem install <gemname> –version <version-number> for example:

gem install bundler --version 1.0.21

You can then list all the installed gems in the selected gemset with the command gem list you can also list all the gemsets and see which one you are currently using with the command rvm gemset list.

The bundler gem lets you create a GemFile to list gems and have them automatically installed when you run bundle install or just bundle for short. You specify the gems to be installed via gem “gemname” and you can specify the version with gem “gemname”, “version” for example:

gem 'rails', '3.2.2'

If you don’t specify a version then bundler will install the latest version. I recommend always specifying a version to avoid some issues. For example, if your application relies on a specific gem version, running bundle install could update to a newer gem and break your application.

Also when working with Rails, make sure every gem you use is specified in your GemFile because some places you may deploy to like Heroku may depend on our GemFile to install the necessary gems. Your app may work fine locally because you manually installed a required gem but when you deploy, Heroku has no way to know your app needs this gem, so it doesn’t get installed and you wonder why your app works locally but not remotely. Because of this I also recommend you do not install any gems into your global gemset.

There is a lot more to RVM, Gemsets, Bundler, and GemFiles then what I have covered, but this should be a good introduction to get a novice up and running.

 


22
Feb 12

Stack Overflow Will Boost Your Career

I am starting a new job, a job that I have Stack Overflow to thank for getting. Not only did Stack Overflow find the job for me (Stack Overflow careers) but it played a major role in my development of the skills necessary for obtaining such a position.

I joined the Q and A site shortly after it launched and have been a pretty active member ever since. I used it to ask questions to direct problems I was facing. I used it to strengthen my communication skills by making sure my questions and answers were written clearly and well thought out. When I was bored I would read popular questions and answers and any time I came across a new technology, principle, or term I was unfamiliar with, I looked it up and gained that knowledge as well. One of the biggest problems when working full time as a solo developer is keeping up with new technologies when you don’t know what you don’t know. I cannot count how many great discoveries I have made that have made my productivity so much better; and that I wouldn’t have even known about if I didn’t see someone else on Stack Overflow reference it.

Of course, I don’t owe everything to Stack Overflow, there are many factors that contributed to my development, and I owe myself a little credit for being determined enough to learn all these things (and I am still learning). However, Stack Overflow did play a major part in that, and for that, I am grateful.


05
Jan 12

Ruby Tutorial – Make a Tic Tac Toe game

I started playing around with Ruby recently and it is a really fun language, albeit the syntax can look a little strange at first. After reading all the fantastic information over at rubylearning.com I wanted to use my new acquired knowledge to build something. I first built a number guessing game, then a hang man game, both very simple and not very interesting. I wasn’t going for interesting though, I was just trying to build some simple things as an exercise. After those two, I built a tic tac toe game, still not extremely interesting but more so than the others because it feels like the game is actually playing against you rather than just telling you whether you’re right or wrong.

You can download the full source
to Ruby Tic Tac Toe here: tic.rb

Lets start by defining our initialize method, the first thing we want to do is create a container to hold the 9 places on the tic tac toe board. It is a 3 by 3 grid so we will name them A,B,C across and 1,2,3 down. Lets store them in a Ruby hash object with their default values set to spaces indicating they are empty:

@places = {
	"a1"=>" ","a2"=>" ","a3"=>" ",
	"b1"=>" ","b2"=>" ","b3"=>" ",
	"c1"=>" ","c2"=>" ","c3"=>" "
}

Now that we have defined the 9 slots, lets define the 8 different possible winning columns. These are the slot  (the 3 horizontal, 3 vertical and 2 diagnal) combinations a player must occupy to win the game. We will store these in an array called @columns where each index is a nested array defining the positions needed to win.

@columns = [
	['a1','a2','a3'],
	['b1','b2','b3'],
	['c1','c2','c3'],
 
	['a1','b1','c1'],
	['a2','b2','c2'],
	['a3','b3','c3'],
 
	['a1','b2','c3'],
	['c1','b2','a3']
]

Next we want to randomly determine who is X and who is O and assign each to a variable called @cpu and @user:

@cpu = rand() > 0.5 ? 'X' : 'O'
@user = @cpu == 'X' ? 'O' : 'X'

Now lets give each player a name; lets name cpu as “Ruby” and ask the user to input their name and store each in the variables @cpu_name and @user_name respectively:

@cpu_name = "Ruby"
put_line
puts "\n	RUBY TIC TAC TOE"
puts "\n What is your name?"
STDOUT.flush
@user_name = gets.chomp
put_bar

You will notice I called two methods here that are not yet defined; put_line and put_bar these are just convenience methods to print a line or a bar. We will define those methods shortly. For the last part of the initialize method we will let whoever is X make their first move:

if(@user == 'X')
	user_turn
else
	cpu_turn
end

That concludes the initialize method, the user_turn and cpu_turn methods are what we will call to allow either the user or the cpu make a move. We will define those later; for now, lets define the put_line and put_bar methods:

def put_line
	puts "-----------------------------------------------------------------------------"
end
 
def put_bar
	puts "#############################################################################"
	puts "#############################################################################"
end

Now we have enough information to define the method that draws our tic tac toe board. Lets also print out the user name and who is X and who is O to make it easier for the player:

def draw_game
	puts ""
	puts "#{@cpu_name}: #{@cpu}"
	puts "#{@user_name}: #{@user}"
	puts ""
	puts "   a b c"
	puts ""
	puts " 1 #{@places["a1"]}|#{@places["b1"]}|#{@places["c1"]}"
	puts "   -----"
	puts " 2 #{@places["a2"]}|#{@places["b2"]}|#{@places["c2"]}"
	puts "   -----"
	puts " 3 #{@places["a3"]}|#{@places["b3"]}|#{@places["c3"]}"
end

Remember, the @places hash defaults to having a space for each slot. When a player makes a move it will then contain it (X or O) instead, automatically drawing it to the board the next time we render it. Now lets define the cpu_turn method:

def cpu_turn
	move = cpu_find_move
	@places[move] = @cpu
	put_line
	puts "#{@cpu_name} marks #{move.upcase}"
	check_game(@user)
end

The first thing happening in this method is we call another function called cpu_find_move (we will define it later) which analyses the positions available to determine the best move. We then assign the returned value to the @places hash and output the move notifying the user what move was just placed. Finally we call a soon to be defined method called check_game which checks to see if anyone has won or if it is a stalemate. This method expects a parameter letting it know whose turn is next if the game is not over.

Before we define the cpu_find_move method lets first define two functions that it relies on to analyse the board. The first function is called times_in_column which expects 2 parameters, the first being an array which is the column (of the columns in the @columns array) on the board we want to analyse and the second is what we are looking for (either X or O); and it will return how many times the item is in the column:

def times_in_column arr, item
	times = 0
	arr.each do |i|
		times += 1 if @places[i] == item
		unless @places[i] == item || @places[i] == " "
			#oppisite piece is in column so column cannot be used for win.
			#therefore, the strategic thing to do is choose a dif column so return 0.
			return 0
		end
	end
	times
end

You will notice we are checking if any of the slots in the column are either a space or the item we are looking for. If they are neither then the only thing it can possibly be is the other player (X or O). And since we cannot win on a column that is occupied by the other player we instantly return 0.

The next function we will define is called empty_in_column this one only expects one parameter which is also a column reference and just returns the first empty slot it finds in the column:

def empty_in_column arr
	arr.each do |i|
		if @places[i] == " "
			return i
		end
	end
end

Now that we have both of those defined we can now define the cpu_find_move method. The first thing we will want to do is determine if there is any move possible that will cause a win and if so, return it. You can determine this by checking to see if any of the @columns that define a win already contain 2 of cpu’s moves which indicates a 3rd move into that column will be a win:

@columns.each do |column|
	if times_in_column(column, @cpu) == 2
		return empty_in_column column
	end
end

If there is no moves available that will cause a win, the next thing to look for is if there are any moves available that will cause a loose. What I mean is, if there is a place the user can move to win. If there is, we need to move there first to block it:

@columns.each do |column|
	if times_in_column(column, @user) == 2
		return empty_in_column column
	end
end

If neither of these find anything then there is no possible winning moves either way. So what we want to do now is build up to a winning move. To do this we check to see if any column has at least one of cpu’s moves so we can add to it:

@columns.each do |column|
	if times_in_column(column, @cpu) == 1
		return empty_in_column column
	end
end

If that didn’t find anything either, then at this point we can just move to any empty slot. To make it seem more natural lets try to find a random empty slot, but if our first random slot is not empty, then at that point lets just move to the first empty slot we find:

#no strategic spot found so just find a random empty
k = @places.keys;
i = rand(k.length)
if @places[k[i]] == " "
	return k[i]
else
	#random selection is taken so just find the first empty slot
	@places.each { |k,v| return k if v == " " }
end

That is the final part of the cpu_find_move function. So we can move on to creating the user_turn method. What we want to do here is draw the board so the user can decide where to move and then ask them to type in the slot they would like to place a move. We need to check their input to make sure it is either a valid move or the word “exit.” If it is a valid move, make the move, or if it is “exit” then exit the program otherwise notify them to try again:

def user_turn
	put_line
	puts "\n	RUBY TIC TAC TOE"
	draw_game
	puts "\n #{@user_name}, please make a move or type 'exit' to quit"
	STDOUT.flush
	input = gets.chomp.downcase
	put_bar
	if input.length == 2
		a = input.split("")
		if(['a','b','c'].include? a[0])
			if(['1','2','3'].include? a[1])
				if @places[input] == " "
					@places[input] = @user
					put_line
					puts "#{@user_name} marks #{input.upcase}"
					check_game(@cpu)
				else
					wrong_move
				end
			else
				wrong_input
			end
		else
			wrong_input
		end
	else
		wrong_input unless input == 'exit'
	end
end

We already know we still need to define the check_game method, we also now need to define the wrong_move and wrong_input methods. If the user input a valid slot but it is not empty then we call wrong_move but if it is not a valid slot at all then we call wrong_input. We define these methods like so:

def wrong_input
	put_line
	puts "Please specify a move with the format 'A1' , 'B3' , 'C2' etc."
	user_turn
end
 
def wrong_move
	put_line
	puts "You must choose an empty slot"
	user_turn
end

Now we can proceed to creating the check_game method. first we will want to see if the cpu or the user has won, if so, output who won and exit the game. If not, check to see if there are any moves left and call the appropriate players turn function, otherwise, output that the game is over and it is a draw. Here is the method definition:

def check_game(next_turn)
 
	game_over = nil
 
	@columns.each do |column|
		# see if cpu has won
		if times_in_column(column, @cpu) == 3
			put_line
			puts "Game Over -- #{@cpu_name} WINS!!!"
			game_over = true
		end
		# see if user has won
		if times_in_column(column, @user) == 3
			put_line
			puts "Game Over -- #{@user_name} WINS!!!"
			game_over = true
		end
	end
 
	unless game_over
		if(moves_left > 0)
			if(next_turn == @user)
				user_turn
			else
				cpu_turn
			end
		else
			put_line
			puts "Game Over -- DRAW!"
		end
	end
end

There is now only one method left to define; the moves_left method that determines if it is a stalemate or not. Here it is:

def moves_left
	slots = 0
	@places.each do |k, v|
		slots += 1 if v == " "
	end
	slots
end

And there you have it, you can now execute this program on the command line and it will play a game of tic tac toe with you.

Like I said, I just started playing with Ruby, so if you see I did something wrong or find a way it could be improved, please feel free to let me know in the comments. I hope you enjoyed the tutorial.


30
Dec 11

Saying goodbye to my maintenance nightmare

In early 2006 I took over a small e-commerce website that was written in vanilla PHP. At the time I was a front-end developer, with no server side coding experience. I initially learned PHP from studying the code on this project for which I was now the sole programmer. For about 4 years I added on to this project taking it from a small e-commerce site to a medium sized and fairly complex site.

The site was designed using Adobe Dreamweaver templates, this was not my design decision by the way, I inherited it that way and never changed it. The way templates work in Dreamweaver is, you have a template file that all pages are based off of. The template defines the general layout with named sections that differ per page. So when you create a new webpage in Dreamweaver and tell it that the page is based off this template, it creates a new page with the contents of the template applied. All the contents however, are disabled with only the named sections editable. So for example, there could be editable fields allowing you to change the title tag, and allowing you to add page content without making any changes to the layout. I thought this sounded good in theory, I mean I could even take it farther by making the header and footer elements in the layout use PHP includes so I could easily update them as well.

The problem is, this isn’t as flexible as it sounds. What if you want to change something in the template? No matter how much you plan to never have to change the template once it is created, you will always need to eventually. The template isn’t a file that gets included or applied at runtime, it needs to be applied locally to all the dependent pages from within Dreamweaver. What this means is, to make 1 small change to the template, you have to apply it to all pages that are based off of it locally and then re upload them all to the server. Also, did I mention that there was no development server! I had tried to set one up but was too inexperienced and didn’t realize why nothing worked the same on the development vs production servers, so I made all changes directly on the live site. Yeah this was bad, not to mention that I had never even heard of version control at the time.

Uploading every webpage (and there were many) after applying a template update was time consuming. And if it messed something up (remember I had no version control or dev server) it could be a while before I got things back on track. In short, making changes to the template (at least in my environment at the time) was dangerous, and I hardly ever did it.

My development environment and work flow was a nightmare, and I new it was a nightmare. The problem was, I was inexperienced, had no mentor, and didn’t know what I could do to make it not a nightmare. So I tried to come up with a plan. I did a lot of research, I bought and read a lot of books and spent a great deal of time on sites like stackoverflow asking lots of questions.

In January of 2011 I started building the successor to the site I had been working on for so long. I rebuilt it in an MVC framework, CakePHP. I now was using version control and had a development server. I was able to launch the new site by October of the same year, just in time for Christmas. After all, we are talking about an e-commerce site here! For the most part the new site has been a major success, and I was finally able to wake up from my maintenance nightmare.

I now have a much better workflow. First of all, I did away with the Dreamweaver templates, in fact, I don’t use Dreamweaver at all anymore (even when I did, I never ever used that horrible design mode). In CakePHP, there is what’s called layout files. The layout file’s intention is the same as the Dreamweaver template, only it works much much better. It actually is applied at runtime, meaning you do not have to re apply any changes to any of your pages after editing the layout, they are applied instantly. I also wouldn’t dream of working on the production server, in fact, I never even setup ftp to the production server. The only way updates get applied to the production site is by pulling those changes from my repository.

It was a lot of work and a great learning experience. With no one to turn to but strangers on forums and myself to learn what was needed, there were many times when I thought I had failed and should give up. I persisted however, and at the end it was all worth it.

 


17
Nov 11

Why background images look blurry on mobile devices

Images are made up of pixels. Different screens have different pixel densities or pixels per inch (PPI). So if an image was 400 pixels wide and displayed on a 100 ppi screen then it would be 4 inches wide. Now if the same image were displayed on a 200 ppi screen, it would be 2 inches wide. In short, the higher the resolution, the smaller images appear on it.

Mobile devices have a much higher ppi than desktop or laptop displays. This is how you can fit the same webpage on a 4 inch phone as a 17 inch computer monitor. The only problem is, its so tiny it becomes very hard to read and an absolute nightmare to navigate. This is why even though modern phones are capable of displaying full fledged web pages many still opt for mobile versions.

When a phone displays a mobile web page it sets its display to a much lower resolution. Otherwise, the web page would still look tiny and only take up about a quarter of the screen. In this lower resolution, everything is sort of stretched to make what should only fill a fraction of the screen, fit the actual screen size. Other elements like text have no problem when doing this but elements like images when scaled get pixelated. In order to prevent pixelation most devices anti alias the image. This works sometimes, but many images with sharp contrasting lines and corners like the example below become blurry looking.

Pixelation and anti aliasing example.

Don’t fret yet, there is a solution. Now that you understand why they are blurry, the solution will make more sense. All you need to do is use an image about 3 times larger than you actually want it to appear. So if you want your image to display sharply at 25px you will need your source image to be 75px. Now you can’t just set your image to 75 pixels, that will cause your image to display as 75px and be strecthed/blurred. You want the 75px image to display at 25px so when it gets stretched it still looks sharp.

There is a new CSS3 property called background-size. You can set the background to an image that is 75 by 75 pixels and then set the background size to 25px:

background-size: 25px 25px;

Doing this, your background will be the same size as if you had used a 25 by 25 pixel image, the only difference is, it now displays sharp.

Background-size is a new feature, but luckily most smart phones (iPhone, Android) use modern browsers that have support for this property. If you want a full proof method you are not going to be able to use actual background images. What you can do however is use an <img> tag with the source pointing to a 75 by 75 pixel image and set the width and height of the image to 25px.


12
Oct 11

Find similar products in MySQL using Levenshtein Distance

Many times on a product page you will want to show the shopper some similar products they might also be interested in. I struggled with this for a long time before I discovered this solution, which I think works very well. The Levenstein distance is a measure of how different 2 comparing strings are.

I found a great implementation for a Levenstein Distance function in MySQL by Jason Rust. I had to make one small adjustment to get MySQL to allow me to create it, which was simply changing the delimiter from ; to something else since the function definition contains semicolons in it.

So the updated code would be:

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
DELIMITER $$
CREATE FUNCTION levenshtein( s1 VARCHAR(255), s2 VARCHAR(255) ) 
  RETURNS INT 
  DETERMINISTIC 
  BEGIN 
    DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT; 
    DECLARE s1_char CHAR; 
    -- max strlen=255 
    DECLARE cv0, cv1 VARBINARY(256); 
    SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2), cv1 = 0x00, j = 1, i = 1, c = 0; 
    IF s1 = s2 THEN 
      RETURN 0; 
    ELSEIF s1_len = 0 THEN 
      RETURN s2_len; 
    ELSEIF s2_len = 0 THEN 
      RETURN s1_len; 
    ELSE 
      WHILE j <= s2_len DO 
        SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1; 
      END WHILE; 
      WHILE i <= s1_len DO 
        SET s1_char = SUBSTRING(s1, i, 1), c = i, cv0 = UNHEX(HEX(i)), j = 1; 
        WHILE j <= s2_len DO 
          SET c = c + 1; 
          IF s1_char = SUBSTRING(s2, j, 1) THEN  
            SET cost = 0; ELSE SET cost = 1; 
          END IF; 
          SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost; 
          IF c > c_temp THEN SET c = c_temp; END IF; 
            SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1; 
            IF c > c_temp THEN  
              SET c = c_temp;  
            END IF; 
            SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1; 
        END WHILE; 
        SET cv1 = cv0, i = i + 1; 
      END WHILE; 
    END IF; 
    RETURN c; 
  END$$

And the helper function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DELIMITER $$
CREATE FUNCTION levenshtein_ratio( s1 VARCHAR(255), s2 VARCHAR(255) ) 
  RETURNS INT 
  DETERMINISTIC 
  BEGIN 
    DECLARE s1_len, s2_len, max_len INT; 
    SET s1_len = LENGTH(s1), s2_len = LENGTH(s2); 
    IF s1_len > s2_len THEN  
      SET max_len = s1_len;  
    ELSE  
      SET max_len = s2_len;  
    END IF; 
    RETURN ROUND((1 - LEVENSHTEIN(s1, s2) / max_len) * 100); 
  END$$

Once you have those defined you can use it to find related products by their vendor style number. Most manufacturers use a common scheme for their style numbers so the closest matching style numbers are usually the most similar products. With that in mind, you could now find simiilar products with a query like this:

1
2
3
4
SELECT *, levenshtein(style_number, "style-number-to-match") as related_score
  FROM products
  ORDER BY related_score
  LIMIT 2

This will give you the 2 most matching products (the first one being itself). Also note, this function is very time consuming so to speed it up I limited it to only products with the same brand. You also want to exclude the same product from returning in the result set.

1
2
3
4
5
6
SELECT *, levenshtein(style_number, "style-number-to-match") as related_score
  FROM products
  WHERE brand = "my-brand"
  AND style_number != "style-number-to-match"
  ORDER BY related_score
  LIMIT 2

Even still, if you have many products this seems too slow to use in production. So what I did was use ajax to find the related products after the page finished loading so it does not make the entire page slow. A small price to pay to get very accurate related products.


21
Sep 11

CakePHP Sluggable Behavior overwrites passed slug value for new Model entries.

The CakePHP Sluggable Behavior is a very handy tool. It lets you auto generate and save a slug for human readable text in a URL.

The way it works is you specify a column to generate the slug from and you specify a column to store the slug in. So if you have a Product model and you specify Product.name to grab the slug from and specify Product.slug to store it in. When you create a new product with my super cool product as the name the Product.slug field will automatically be populated with my-super-cool-product.

However, if you decide you want to specify your own slug when creating a new product it will overwrite what you input with the generated slug.

If you look at the code in the SluggableBehavior class in the beforeSave function you will find this part that decides whether or not to generate a slug:

// See if we should be generating a slug 
 if ($Model->hasField($this->__settings[$Model->alias]['slug']) && ($this->__settings[$Model->alias]['overwrite'] || empty($Model->id))) 
 {

What this does is, first, see if the model in question has the appropriate field to store the slug in. If so, then it checks if either the overwrite property is set to true or if this is a new model being generated. Basically that means it only generates a slug when creating a new Product (or whatever you are creating) and not when you are modifying one unless the overwrite setting is set to true.

As you can see when creating a new entry, it does not care if you manually set your own slug it will generate one for you anyways. The way you can fix this is by updating this expression to check if a slug was passed before generating one.

The new code looks like this:

// See if we should be generating a slug
if ($Model->hasField($this->__settings[$Model->alias]['slug'])
&& ($this->__settings[$Model->alias]['overwrite'] || empty($Model->id))
&& empty($Model->data[$Model->alias][$this->__settings[$Model->alias]['slug']]))
{

Now if you do not set your own slug it will behave normally but if you do set your own slug when creating a new entry it will use that instead of ovewriting it with a generated one.


14
Sep 11

CakePHP script keeps executing twice

I noticed my CakePHP script was loading multiple times. I noticed this because I created a behavior to keep track of how popular different categories are and I noticed that every time I visited one it was counting as 2 visits. So I checked the Apache log files, to see what was being requested. It turned out when a page was being requested via the URL:

/categories/513/rings

An additional request was also being made for:

/categories/513/favicon.ico

I looked at my layout file where it sets the favicon and it looked like this:

<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">

This tells the server to load the file favicon.ico from the current directory. Which based on the URL would be /categories/513/ so it tries to load it as /categories/513/favicon.ico and causes our script to execute twice. The fix for this is very easy. Just prefix the URL with a forward slash which specifies it is an absolulte URL rather than a relative one:

<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

Now my script only executes once per page visit. If this is happening to you, its likely the favicon file as well, however it could also be a CSS, JavaScript, image or any other resource your page is requesting. You may have to do a little research to find out. I recommend loading a page then checking the Apache log files to see what is being requested.


08
Aug 11

Simple and Easy Alternative to ACL in CakePHP

I think ACLs in CakePHP have their place, but for some uses it can be overkill. Lets say you just have a few groups of users that have different access levels (e.g. customers, employees, admins). Each user group is like a level and can access anything lower levels can access. So employees can access anything customers can, and admins can access anything employess can.

If the users table has a column called role which is saved to the session Auth.User.role after logging in. We can create a component to determine a users role like this:

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
class UserRoleComponent extends Object
{
	public $components = array('Session');
 
	private $role = NULL;
 
	private $score = array(
		'customer'  => 0,
		'employee' => 1,
		'admin'   => 2,
	);
 
	public function role()
	{
		if($this->role == NULL)
		{
			$this->role = strtolower($this->Session->read('Auth.User.role')) ?: 'customer';
		}
		return $this->role;
	}
 
	public function is($role)
	{
		return $this->score[$this->role()] >= $this->score[$role];
	}
}

Now you can use this component inside your controllers like $this->UserRole->is(“employee”); and will return true for both employees and admins but will return false for customers. You can also easily add new roles in the future without messing anything up since the score for each role is only used internally by the component.

Now that we have that part set up, we don’t want to riddle our code with role checks everywhere. Lets make it a little more auto-magic. I want to be able to add a public variable to a controller that specifies what roles have access to what actions. something like this:

1
2
3
4
5
public $accessLevels = array(
	'*' => 'customer',
	'edit' => 'employee',
	'delete' => 'admin',
);

In the controller this is set on, users would be able to access all actions except edit and delete. Employees would be able to access all actions except delete and admins would be able to access every action.

We need to edit the AppController to make this work:

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
public function beforeFilter() 
{
	parent::beforeFilter();
 
	if (!empty ($this->accessLevels))
	{
		$this->Auth->authorize = 'controller';
		$this->Auth->deny('*');
	}
	else
	{
		$this->Auth->allow('*');
	}
}    
 
public function isAuthorized() 
{   
	//controller defined access levels
	if(!empty ($this->accessLevels))
	{
		//get current action
		$currentAction = $this->params['action'];
		//see if current action is specified for access
		if(!empty ($this->accessLevels[$currentAction]))
		{
			$accessLevel = $this->accessLevels[$currentAction];
		}
		//see if wildcard is specified for access
		else if(!empty ($this->accessLevels['*']))
		{
			$accessLevel = $this->accessLevels['*'];
		}
		//see if user qualifies for access
		if(!empty ($accessLevel))
		{
			return $this->UserRole->is($accessLevel);
		}
	}
	//no access level set, default to true
	return true;	
}

This code assumes you are using the Auth component. Instructions on setting up the Auth component are beyond the scope of this tutorial.

That is pretty much it. If you want to take things a bit further you can extend the Html Helper to automatically detect links that are pointing to pages that the current user is not authorized for and set them as disabled.