环境: ruby 1.8.7 + rails 2.3.x, ruby 1.9.2 + rails 3.x
前段时间处理手机客户端post请求问题, 经常遇到csrf token 问题, 另外web上也经常遇到和session相关的问题, 不深究下去, 很多东西云里雾里, 于是把rails源码csrf_token, 和 session cookiestore 相关的代码研究了下.
csrf_token 原理
这个相信做rails的, 没有不知道的,是rails framework中为了防止 XSS攻击的, 可是你知道它的原理吗?
好, 顺着代码跟进去, 这是 rails 3.x 的源码.
入口就是 ApplicationController 中的 protect_from_forgery
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 85 def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token prepend_before_filter :verify_authenticity_token, options end
再找到 verify_authenticity_token
def verify_authenticity_token verified_request? || handle_unverified_request end
再找到 verified_request?
发现判断合法的请求方法:
1, 跳过不验证的
2, GET 请求
3, csrf_token 和参数中 authenticity_token 值相同的
4, http header 中 X-CSRF-Token 和 csrf_token 的值相同的
不合法请求会 reset_session
def handle_unverified_request reset_session end
来看看 rails 2.x 的, 为什么说 rails 2.x 的,和 rails 3.x 不一样, 影响也很大.
合法的请求:
1, 跳过检查的
2, GET 请求
3, ajax 请求
4, 不是 html 格式请求, 例如 json, xml 格式请求都是合法的
5, csrf_token 值和 参数中 authenticity_token 值相同的
不合法的请求会 raise error
def verify_authenticity_token verified_request? || raise(ActionController::InvalidAuthenticityToken) end
上面的处理都有漏洞, 来看看
rails 3.x 的, 假如用户登录是 post, 登录前还没有 session ,此时会 reset_ssession,因为本来就没有登录后的session,reset_session后,后面的代码继续执行, 假如用户知道用户用户名,密码,利用http client 工具就可以成功获得登录后的session, 虽然 csrf 会验证失败, 所以可以自己打个patch使用 rails 2.x 的方式, 直接 raise
rails2.x 的当请求格式不是 html,是 json 就可以成功跳过 csrf 验证, 例如我这个更新redmine的脚本就是利用这个漏洞实现的.
https://gist.github.com/wxianfeng/5070599
那么 csrf_token 的值又是存在什么地方的呢, 在 session[:_csrf_token],rails 默认session是 cookie store, 这就涉及到cookiestore原理了.
关于 csrf_token 还有一个需要注意的地方, 在 test env 下是不需要 csrf_token 的, 顺着 csrf_meta_tag 跟进去可以看到.
Rails Session CookieStore 原理
在rails后端调试下 session, 打印出来的结果是一个hash, 以github 为例, 先反向得到 session 数据, 用firebug可以看到github的cookie中有一个 _gh_session, 如下:
_gh_sess=BAh7CjoPc2Vzc2lvbl9pZCIlMjM1OGMwZjFhYmU2MTQ0MGRlYWUzYWVhODVhM2U2MTk6EF9jc3JmX3Rva2VuSSIxcHNLWEFoYittaXVVVnZXU3BxMDBJaE52Z0QvQ0kyYjg1cU5pNTJMU2R6TT0GOgZFRjoJdXNlcmkD2IsBOhBmaW5nZXJwcmludCIlZGFmNjBhOGFlYTJlZWE3YThjNWY1OGRmMzg2YzhhNWQ6DGNvbnRleHRJIgYvBjsHRg%3D%3D--0320c02623b8a27a66bbbcd38d095511c459e1f3;
取出 — 前面的部分, 假设为 data
::Marshal.load ::Base64.decode64(data) 后会得到一个hash, 这个就是后端的 session数据
ruby-1.9.2-p290 :016 > data = "BAh7CjoPc2Vzc2lvbl9pZCIlMjM1OGMwZjFhYmU2MTQ0MGRlYWUzYWVhODVhM2U2MTk6EF9jc3JmX3Rva2VuSSIxcHNLWEFoYittaXVVVnZXU3BxMDBJaE52Z0QvQ0kyYjg1cU5pNTJMU2R6TT0GOgZFRjoJdXNlcmkD2IsBOhBmaW5nZXJwcmludCIlZGFmNjBhOGFlYTJlZWE3YThjNWY1OGRmMzg2YzhhNWQ6DGNvbnRleHRJIgYvBjsHRg%3D%3D" => "BAh7CjoPc2Vzc2lvbl9pZCIlMjM1OGMwZjFhYmU2MTQ0MGRlYWUzYWVhODVhM2U2MTk6EF9jc3JmX3Rva2VuSSIxcHNLWEFoYittaXVVVnZXU3BxMDBJaE52Z0QvQ0kyYjg1cU5pNTJMU2R6TT0GOgZFRjoJdXNlcmkD2IsBOhBmaW5nZXJwcmludCIlZGFmNjBhOGFlYTJlZWE3YThjNWY1OGRmMzg2YzhhNWQ6DGNvbnRleHRJIgYvBjsHRg%3D%3D" ruby-1.9.2-p290 :017 > ::Marshal.load ::Base64.decode64(data) => {:session_id=>"2358c0f1abe61440deae3aea85a3e619", :_csrf_token=>"psKXAhb+miuUVvWSpq00IhNvgD/CI2b85qNi52LSdzM=", :user=>101336, :fingerprint=>"daf60a8aea2eea7a8c5f58df386c8a5d", :context=>"/"}
再来正向生成
data = ::Base64.encode64 Marshal.dump(h)
那—后面的 digest 是怎么生成的, 和rails中 secrect 合起来加密生成的, 这样别人就不能伪造cookie了,术语叫 cookie 签名.
大概生成算法是这样:
session = {"_csrf_token"="xxxxx","user_id"=>4} session_data = ::Base64.encode64 Marshal.dump(session) session_data = "#{session_data}--#{generate_hmac(session_data, @secrets.first)}" def generate_hmac(data, secret) OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data) end
源码位置:
/Users/wangxianfeng/.rvm/gems/ruby-1.9.2-p290/gems/rack-1.4.3/lib/rack/session/cookie.rb /home/wxianfeng/.rvm/gems/ruby-1.9.2-p320/gems/activesupport-3.2.2/lib/active_support/message_verifier.rb
总结起来一句话, rails 的session cookiestore 是存在浏览器的cookie中的, 由 http 协议的 headers 带到后端, 后端解包出来的.
OVER, 是不是没想到啊?
环境: mac 10.8 + ruby 1.9.2
ruby 对象模型非常重要, 不了解ruby对象模型, 不算真正掌握ruby, ruby 号称一切都是对象, why? 当你知道对象模型就清楚了.
说到ruby对象模型, 这张图是ruby基本的类关系.
class 表示实例关系(水平),super 表示继承关系(垂直),从图中可以看出
objmy 是 MyClass 的实例
MyClass 是 Class 的实例
Class 的父类是 Module
MyClass 的父类是 Object
Object 的父类是 BasicObject (ruby1.9)
MyClass include 模块M后,相当于在继承祖先链正上方继承该模块
Object include 模块Kernel
Module类的superclass 是Object
所有都可以在irb看到:
ruby-1.9.2-p290 :001 > MyClass = Class.new => MyClass ruby-1.9.2-p290 :002 > objmy = MyClass.new => #<MyClass:0x007f9ea1aba080> ruby-1.9.2-p290 :003 > objmy.class => MyClass ruby-1.9.2-p290 :004 > MyClass.class => Class ruby-1.9.2-p290 :005 > MyClass.superclass => Object ruby-1.9.2-p290 :006 > Class.class => Class ruby-1.9.2-p290 :007 > Class.superclass => Module ruby-1.9.2-p290 :008 > Module.class => Class ruby-1.9.2-p290 :009 > Module.superclass => Object ruby-1.9.2-p290 :010 > Object.class => Class ruby-1.9.2-p290 :011 > Object.superclass => BasicObject ruby-1.9.2-p290 :012 > BasicObject.class => Class ruby-1.9.2-p290 :013 > BasicObject.superclass => nil
所以掌握了 Object,Module,Class 三者之间关系很重要, ruby 很多gem里的方法都是打开 Object来注入方法的.这样下面任何对象就都有该实例方法了.
demo:
#!/usr/bin/env ruby class Object def foo p "im foo" end end MyClass = Class.new objmy = MyClass.new objmy.foo # im foo
有一个gem可以查看ruby对象模型, drx
安装:
>gem install drx >brew install graphviz
使用:
wxianfeng-2:entos wangxianfeng$ irb ruby-1.9.2-p290 :001 > require 'drx' => true ruby-1.9.2-p290 :002 > MyClass = Class.new => MyClass ruby-1.9.2-p290 :003 > objmy = MyClass.new => #<MyClass:0x007fcd1221b2e0> ruby-1.9.2-p290 :004 > objmy.see
出现下图:
这样就更加一目了然了.但是注意上图引入了eigenclass概念, 想了解 ruby 中方法查找的话, 就必须要知道了, 下回分解.
很久以前从项目中抽取出来的 汉字转拼音的 gem, 今天介绍下使用方法.
功能
- 首字母支持
- 全拼支持
- 多音字支持
- 其它字符默认输出
Gemfile:
gem 'hanzi_to_pinyin', '0.8.0', require: 'hanzi_to_pinyin'
使用demo
$ HanziToPinyin.hanzi_to_pinyin("喜欢Ruby") => "xhruby" $ HanziToPinyin.hanzi_2_pinyin("喜欢Ruby") => "xhruby" $ HanziToPinyin.is_hanzi?("你") => true $ HanziToPinyin.is_hanzi?("a") => false # 多音字,分隔 字字之间;分隔,字母丢弃 $ HanziToPinyin.hanzi_2_py("我们") => "wo;men" $ HanziToPinyin.hanzi_2_py("查理Smith") => "cha,zha;li" $ HanziToPinyin.hanzi_2_py("测试1") => "ce;shi;1" $ HanziToPinyin.hanzi_2_py("测_试") => "ce;_;shi" $ HanziToPinyin.hanzi_2_py("测-试") => "ce;-;shi" $ HanziToPinyin.hanzi_2_py(2) => "2" $ HanziToPinyin.is_number?("1".ord) => true $ HanziToPinyin.is_number?("a".ord) => false $ HanziToPinyin.is_underline?("_".ord) => true $ HanziToPinyin.is_underline?("豆豆") => false $ HanziToPinyin.is_dash?("-".ord) => true
更多介绍可以看 github 主页
另外也被翻译成了 nodejs module
情况是这样的, 公司内部测试服务器经常需要更新代码供测试人员使用网站, 每次都是我们后端开发人员部署的, 这样就加大了工作量,效率低下,话说我们部署也是使用capistrano 的, 只需一条命令就可以顺利部署, 但是还是不如非开发人员部署来的方便,于是就有了 rake_ui
rake_ui gem 是我发布的,但是是在修改别人代码的基础上发布的,下面介绍使用方法:
首先看下效果图:
1, 环境
Node.js Socket.io Rails 3.x
2,Gemfile
gem 'rake_ui', '0.6.0'
3, 在你的 routes.rb 中添加路由
Rails.application.routes.draw do mount RakeUi::Engine => "/rake_ui" end
4, 配置 config/rake_ui.yml
host: '192.168.10.107' log: '/data/projects/entos/log/rake.log'
host是你的ip地址,Nodejs 要用, log 是你项目下log目录下rake.log 会被自动创建
5, 配置 config/tasks.yml
- 'rake about' - 'rake routes'
把你需要执行的rake任务写在这个 yaml 中
6, 启动 nodejs server
rake start_node_server
ok, 你可以访问 /rake_ui 看到你的 web gui 界面了, 把你的部署方案写在rake任务中, 然后在这个界面可以点击部署.
该gem有可能被更新,看到最新的说明请移到步这里:
最近项目中常用到 Array#pack, String#unpack 方法,在此总结下:
Array#pack, String#unpack 可以实现不同编码之间的处理, 可以处理字节级, bit 级的一些二进制格式.
字节编码, ruby里主要是 “\nnn” 和 “\xnn” 的形式, nnn 是八进制数字, nn 是十六进制, 可以从 <<ruby编程语言>> 这本书看到相关信息.
截了张书中的图:
例子:
ASCII 码值: 0123456789
字节编码:
“\000\001\002\003\004\005\006\007\010\011” (八进制)
“\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09” (十六进制)
另外 ascii 码值是 7,8,9 的在ruby中是转义序列 “\a”, “\b”, “\t”, 所以字节编码也可以写成
\x00\x01\x02\x03\x04\x05\x06\a\b\t
1.9.2p290 :198 > a = [0,1,2,3,4,5,6,7,8,9] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1.9.2p290 :199 > a.pack("c*") => "\x00\x01\x02\x03\x04\x05\x06\a\b\t" 1.9.2p290 :200 > "\x00\x01\x02\x03\x04\x05\x06\a\b\t" == "\000\001\002\003\004\005\006\007\010\011" => true 1.9.2p290 :201 > "\011" == "\x09" => true 1.9.2p290 :202 > "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" == "\000\001\002\003\004\005\006\007\010\011" => true
下面来看一些实例,以字符模板来讲
1, M
M | String | quoted printable, MIME encoding (see RFC2045)
字符串和 quoted printable 编码之间转换,常用在邮件编码中
Array#pack ruby-1.9.2-p290 :132 > ["[www.blog.wxianfeng.com]欢迎您注册,请您激活"].pack("M") => "[www.blog.wxianfeng.com]=E6=AC=A2=E8=BF=8E=E6=82=A8=E6=B3=A8=E5=86=8C,=E8=AF=B7=\n=E6=82=A8=E6=BF=80=E6=B4=BB=\n"
可以看到 每76个字符就多了一个 =\n , 所以如果是用在邮件的 Subject 的中的话, 应该是
["str"].pack("M").gsub(/=\n/,"")
完整的邮件Subject编码应该是 像这样:
value = ["[#{site}]请激活您的帐号"].pack("M").gsub(/=\n/, "") subject = "=?UTF-8?Q?#{value}?="
String#unpack ruby-1.9.2-p290 :153 > "[www.blog.wxianfeng.com]=E6=AC=A2=E8=BF=8E=E6=82=A8=E6=B3=A8=E5=86=8C,=E8=AF=B7=\n=E6=82=A8=E6=BF=80=E6=B4=BB=\n".unpack("M") => ["[www.blog.wxianfeng.com]\xE6\xAC\xA2\xE8\xBF\x8E\xE6\x82\xA8\xE6\xB3\xA8\xE5\x86\x8C,\xE8\xAF\xB7\xE6\x82\xA8\xE6\xBF\x80\xE6\xB4\xBB"]
2, m
m | String | base64 encoded string (see RFC 2045, count is width) | | (if count is 0, no line feed are added, see RFC 4648)
字符串和 Base64 编码之间转换
Array#pack ruby-1.9.2-p290 :133 > ["[www.blog.wxianfeng.com]欢迎您注册,请您激活"].pack("m") => "W3d3dy53eGlhbmZlbmcuY29tXeasoui/juaCqOazqOWGjCzor7fmgqjmv4Dm\ntLs=\n"
base64编码也可以用在邮件编码中,例如用在Subject中就是这样:
value = ["[#{site}]请激活您的帐号"].pack("M").gsub(/=\n/, "") subject = "=?UTF-8?B?#{value}?="
String#unpack ruby-1.9.2-p290 :155 > "W3d3dy53eGlhbmZlbmcuY29tXeasoui/juaCqOazqOWGjCzor7fmgqjmv4Dm\ntLs=\n".unpack("m") => ["[www.blog.wxianfeng.com]\xE6\xAC\xA2\xE8\xBF\x8E\xE6\x82\xA8\xE6\xB3\xA8\xE5\x86\x8C,\xE8\xAF\xB7\xE6\x82\xA8\xE6\xBF\x80\xE6\xB4\xBB"]
3, L
L | Integer | 32-bit unsigned, native endian (uint32_t)
整型(ASCII)和二进制字符串相互转化,int是32为无符号的,占4个字节
Array#pack ruby-1.9.2-p290 :139 > [65].pack("L") => "A\x00\x00\x00" String#unpack ruby-1.9.2-p290 :140 > "A\x00\x00\x00".unpack("L") => [65]
4, c
c | Integer | 8-bit signed (signed char)
整型(ASCII)和二进制字符串相互转化,int 是8位有符号的,占一个字节
Array#pack ruby-1.9.2-p290 :142 > [77].pack("c") => "M" String#unpack ruby-1.9.2-p290 :143 > "M".unpack("c") => [77]
5, Q
Q | Integer | 64-bit unsigned, native endian (uint64_t)
整型和二进制字符串相互转化,int是64位无符号的,占8字节
Array#pack ruby-1.9.2-p290 :149 > [1338053358065].pack("Q") => "\xF1\xF11\x8A7\x01\x00\x00" String#unpack ruby-1.9.2-p290 :150 > "\xF1\xF11\x8A7\x01\x00\x00".unpack("Q") => [1338053358065]
6, S
S | Integer | 16-bit unsigned, native endian (uint16_t)
整型和二进制字符串转化,int是16位无符号的,占2个字节
Array#pack ruby-1.9.2-p290 :151 > [6].pack("S") => "\x06\x00" String#unpack ruby-1.9.2-p290 :152 > "\x06\x00".unpack("S") => [6]
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-pack
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-unpack
http://www.cnblogs.com/baochun968/archive/2011/10/19/2218008.html
核心使用 task 指令 实现多机部署
# encoding:utf-8 # >cap local deploy # >cap remote deploy set :application, "entos" set :deploy_to, "/data/projects/entos" set :scm, "git" set :repository, "git@114.255.155.167:entos.git" set :branch, "master" set :use_sudo, false set :rails_env,"production" task :remote do set :user, "entsea" set :deploy_via, :remote_cache set :copy_exclude, %w(external) server "114.255.155.166", :web, :app, :db, :primary => true end task :local do set :user, 'zzq' set :deploy_via, :remote_cache set :copy_exclude, %w(external) server '192.168.10.105', :web, :app, :db, :primary => true end namespace :deploy do task :start do; end task :stop do; end desc "Creating ln -s , example: database.yml" task :create_sync do run "ln -s #{shared_path}/config/database.yml #{current_path}/config/database.yml" end desc "Restarting unicorn" task :restart, :roles => :app, :except => { :no_release => true } do # run "/bin/sh restart_server.sh" end end after "deploy:symlink", "deploy:create_sync"
今天一个同事 问我 正则 里 ?: 什么意思,记得 以前知道的 , 愣是 忘记了 ,查了下 是非捕获组的意思!
捕获组
()内的是分组,可以用 $1,$2…取值, 被存在了内存中, 留反向引用.
demo:
ruby-1.9.2-p290 :022 > "abcabc".match(/(abc)/) => #<MatchData "abc" 1:"abc"> ruby-1.9.2-p290 :023 > $1 => "abc"
非捕获组
(?:) 内的不当作分组, 分组内内容,内存中没有.
demo:
ruby-1.9.2-p290 :024 > "abcabc".match(/(?:abc)/) => #<MatchData "abc"> ruby-1.9.2-p290 :025 > $1 => nil
那么这个有什么用呢, 可以节省内存
demo
ruby-1.9.2-p290 :033 > "hello" =~ /h(i|ello)/ => 0 ruby-1.9.2-p290 :033 > $1 => "ello" ruby-1.9.2-p290 :034 > "hello" =~ /h(?:i|ello)/ => 0 ruby-1.9.2-p290 :035 > $1 => nil
时间: 2011-07-24
收获:
发现北京ROR的公司不是一般的多,签到单上看到N多公司,技术上没有太大收获,都是介绍性的,没有实战性的,内容主要涉及: mirah , Mongodb,Erlang,Grape
进程:http://www.surveymonkey.com/s/MSY2L7T
PS : 798 很好玩,很有艺术特色
现场:
798 入口
Ruby活动地方
Rails rumble 创始人
现场job board
现场
清一色老外,清一色Mac
介绍Mirah
798
798
798
一个rails2.x 的项目,需要迁移到rails3.x , ruby 1.9.2的编码问题,需要在rb文件头添加指定编码, 常见指定方式如下:
#coding:utf-8 #encoding:utf-8 # -*- coding: utf-8 -*- # -*- encoding: utf-8 -*-
那么多rb文件总不能一个一个加吧,写个shell解决之!!!
yaml 的语法真是变态 , 表示个数组这么麻烦, 更复杂的数据结构 那不是更麻烦 !!!
yaml 文件:
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: name: MyString orgunit_id: 1 inheritable: false # codes 是yaml数组表示方法 # 缩进只能是两个空格为一级,不能是其他字符 codes: - 1 - a - 2 - b - 3 - c
ruby 解析yaml:
ruby-1.9.2-p0 > file = "#{Rails.root}/test/fixtures/enumerations.yml" => "/usr/local/system/projects/entos/ent_os/test/fixtures/enumerations.yml" ruby-1.9.2-p0 > YAML.load File.read(file) => {"one"=>{"name"=>"MyString", "orgunit_id"=>1, "inheritable"=>false, "codes"=>[1, "a", 2, "b", 3, "c"]}}
不知道怎么写的可以 使用 to_yaml 方法 看一下:
irb(main):001:0> => {"one"=>{"name"=>"MyString", "inheritable"=>false, "orgunit_id"=>1, "codes"=>[1, "a", 2, "b", 3, "c"]}} irb(main):002:0> require "yaml" => true irb(main):003:0> hsh.to_yaml => "--- \none: \n name: MyString\n inheritable: false\n orgunit_id: 1\n codes: \n - 1\n - a\n - 2\n - b\n - 3\n - c\n"
可读性 更好的 使用 y 方法
ruby-1.9.2-p0 > y hsh --- one: name: MyString orgunit_id: 1 inheritable: false codes: - 1 - a - 2 - b - 3 - c => nil