Improve Cookie- based Session with Decorator Pattern @ ConFoo - - PowerPoint PPT Presentation

improve cookie based session with decorator pattern
SMART_READER_LITE
LIVE PREVIEW

Improve Cookie- based Session with Decorator Pattern @ ConFoo - - PowerPoint PPT Presentation

Improve Cookie- based Session with Decorator Pattern @ ConFoo Montreal 2018-03-08 by Jian Weihang Improve Cookie-based Session with Decorator Pattern 1 Bonjour! Improve Cookie-based Session with Decorator Pattern 2 Jian Weihang


slide-1
SLIDE 1

Improve Cookie- based Session with Decorator Pattern

@ ConFoo Montreal 2018-03-08 by Jian Weihang Improve Cookie-based Session with Decorator Pattern 1
slide-2
SLIDE 2

Bonjour!

Improve Cookie-based Session with Decorator Pattern 2
slide-3
SLIDE 3
  • Jian Weihang
Improve Cookie-based Session with Decorator Pattern 3
slide-4
SLIDE 4

@tonytonyjan

Improve Cookie-based Session with Decorator Pattern 4
slide-5
SLIDE 5

Taiwan

Improve Cookie-based Session with Decorator Pattern 5
slide-6
SLIDE 6 Improve Cookie-based Session with Decorator Pattern 6
slide-7
SLIDE 7 Improve Cookie-based Session with Decorator Pattern 7
slide-8
SLIDE 8 Improve Cookie-based Session with Decorator Pattern 8
slide-9
SLIDE 9

$ gem install taiwan

Improve Cookie-based Session with Decorator Pattern 9
slide-10
SLIDE 10

Tech Leader

Improve Cookie-based Session with Decorator Pattern 10
slide-11
SLIDE 11

Ruby Developer

since 2010 Improve Cookie-based Session with Decorator Pattern 11
slide-12
SLIDE 12

Maintainer

  • f exif and jaro_winkler
Improve Cookie-based Session with Decorator Pattern 12
slide-13
SLIDE 13

Published a book

in 2015 Improve Cookie-based Session with Decorator Pattern 13
slide-14
SLIDE 14

Improve Cookie- based Session with Decorator Pattern

Improve Cookie-based Session with Decorator Pattern 14
slide-15
SLIDE 15

Outline

  • Introduction of Decorator Pattern
  • Security of Rack::Session::Cookie
  • Encryption of Rack::Session::Cookie
Improve Cookie-based Session with Decorator Pattern 15
slide-16
SLIDE 16

Decorator Pattern

Improve Cookie-based Session with Decorator Pattern 16
slide-17
SLIDE 17

Decorator Pattern

Attach additional responsibilities to an
  • bject dynamically. Decorators provide a
flexible alternative to subclassing for extending functionality. — Design Patterns by the Gang of Four Improve Cookie-based Session with Decorator Pattern 17
slide-18
SLIDE 18

Using Inheritance

class CoffeeWithSugar < Coffee def cost super + 0.2 end end class CoffeeWithMilkAndSugar < Coffee def cost super + 0.4 + 0.2 end end Improve Cookie-based Session with Decorator Pattern 18
slide-19
SLIDE 19

What's the problem?

  • Cannot customize during runtime.
  • Cannot control how and when to decorate a component.
  • ex. double milk?
  • It is tightly coupled.
Improve Cookie-based Session with Decorator Pattern 19
slide-20
SLIDE 20

What's the problem?

  • Cannot customize during runtime.
  • Cannot control how and when to decorate a component.
  • ex. double milk?
  • It is tightly coupled.
Improve Cookie-based Session with Decorator Pattern 20
slide-21
SLIDE 21

What's the problem?

  • Cannot customize during runtime.
  • Cannot control how and when to decorate a component.
  • ex. double milk?
  • It is tightly coupled.
Improve Cookie-based Session with Decorator Pattern 21
slide-22
SLIDE 22

Decorator Pattern in Ruby

class Milk def initialize(coffee); @coffee = coffee end def cost; @coffee.cost + 0.4 end end class Sugar def initialize(coffee); @coffee = coffee end def cost; @coffee.cost + 0.2 end end coffee = Coffee.new # coffee.cost = 2.0 Sugar.new(Milk.new(coffee)).cost # 2.6 Sugar.new(Sugar.new(coffee)).cost # 2.4 Improve Cookie-based Session with Decorator Pattern 22
slide-23
SLIDE 23

Benefits

  • Plain Old Ruby Object.
  • Can be wrapped infinitely.
  • Can use same decorator more than once on component.
  • Can customize in runtime.
Improve Cookie-based Session with Decorator Pattern 23
slide-24
SLIDE 24

Benefits

  • Plain Old Ruby Object.
  • Can be wrapped infinitely.
  • Can use same decorator more than once on component.
  • Can customize in runtime.
Improve Cookie-based Session with Decorator Pattern 24
slide-25
SLIDE 25

Benefits

  • Plain Old Ruby Object.
  • Can be wrapped infinitely.
  • Can use same decorator more than once on component.
  • Can customize in runtime.
Improve Cookie-based Session with Decorator Pattern 25
slide-26
SLIDE 26

Benefits

  • Plain Old Ruby Object.
  • Can be wrapped infinitely.
  • Can use same decorator more than once on component.
  • Can customize in runtime.
Improve Cookie-based Session with Decorator Pattern 26
slide-27
SLIDE 27 class Additive def initialize(coffee) @coffee = coffee end def cost raise NotImplementedError end end class Salt < Additive def cost @coffee.cost + 0.1 end end Improve Cookie-based Session with Decorator Pattern 27
slide-28
SLIDE 28

Rack

Improve Cookie-based Session with Decorator Pattern 28
slide-29
SLIDE 29

Is Rack::Session::Cookie secure?

Improve Cookie-based Session with Decorator Pattern 29
slide-30
SLIDE 30

Simple HTTP Server

require 'rack' app = lambda do |env| session = env['rack.session'] session[:name] = 'tonytonyjan' session[:age] = 28 [200, {}, ['it works']] end app = Rack::Builder.app(app) do use Rack::Session::Cookie, secret: 'secret' end Rack::Handler::WEBrick.run app, Port: ARGV[0] Improve Cookie-based Session with Decorator Pattern 30
slide-31
SLIDE 31

Experiment

Improve Cookie-based Session with Decorator Pattern 31
slide-32
SLIDE 32

Decode

Marshal.load( Base64.decode64( URI.decode_www_form_component(cookie) .split('--') .first ) ) Improve Cookie-based Session with Decorator Pattern 32
slide-33
SLIDE 33

Structure of Rack Cookie Session

+------------- uri encode --------------+ | +------ base64 ------+ +- hex -+ | | | +- Marshal.dump -+ | | | | | | | object | | "--" | mac | | | | +----------------+ | | | | | +--------------------+ +-------+ | +---------------------------------------+ Improve Cookie-based Session with Decorator Pattern 33
slide-34
SLIDE 34

Decode and Verify

def decode_cookie(cookie, secret) cookie = URI.decode_www_form_component(cookie) data, hmac = cookie.split('--') computed_hmac = OpenSSL::HMAC.hexdigest( OpenSSL::Digest::SHA1.new, secret, data ) raise 'invalid hmac' unless computed_hmac == hmac Marshal.load(Base64.decode64(data)) end Improve Cookie-based Session with Decorator Pattern 34
slide-35
SLIDE 35

Is Rack::Session::Cookie secure?

Improve Cookie-based Session with Decorator Pattern 35
slide-36
SLIDE 36

Not Exactly

Improve Cookie-based Session with Decorator Pattern 36
slide-37
SLIDE 37

Rack::Session::Cookie

  • Sign with HMAC-SHA1.
  • No encryption.
Improve Cookie-based Session with Decorator Pattern 37
slide-38
SLIDE 38

Rack::Session::Cookie

  • Sign with HMAC-SHA1.
  • No encryption.
Improve Cookie-based Session with Decorator Pattern 38
slide-39
SLIDE 39

Rack::Session::Cookie

  • Sign with HMAC-SHA1.
  • No encryption.
Improve Cookie-based Session with Decorator Pattern 39
slide-40
SLIDE 40

Sinatra

Improve Cookie-based Session with Decorator Pattern 40
slide-41
SLIDE 41

Sinatra Official Document

Improve Cookie-based Session with Decorator Pattern 41
slide-42
SLIDE 42 require 'sinatra' set :sessions, true set :session_secret, 'set secret' get '/' do session = env['rack.session'] session[:name] = 'tonytonyjan' session[:age] = 28 'Hello world!' end Improve Cookie-based Session with Decorator Pattern 42
slide-43
SLIDE 43

Experiment

Improve Cookie-based Session with Decorator Pattern 43
slide-44
SLIDE 44 Improve Cookie-based Session with Decorator Pattern 44
slide-45
SLIDE 45 Improve Cookie-based Session with Decorator Pattern 45
slide-46
SLIDE 46

Rails

Improve Cookie-based Session with Decorator Pattern 46
slide-47
SLIDE 47

Convention over Configuration

Improve Cookie-based Session with Decorator Pattern 47
slide-48
SLIDE 48

Structure of Rails Cookie

+-------------------- uri encode -------------------+ | +------------ base64 ------------+ +- hex -+ | | | +- base64 -+ +- base64 -+ | | | | | | | JSON | "--" | iv | | "--" | mac | | | | +----------+ +----------+ | | | | | +--------------------------------+ +-------+ | +---------------------------------------------------+ Improve Cookie-based Session with Decorator Pattern 48
slide-49
SLIDE 49

Decode with ActiveSupport

require 'uri' require 'json' require 'active_support' def verify_and_decrypt_session_cookie(cookie, secret_key_base) cookie = URI.decode_www_form_component(cookie) salt = 'encrypted cookie' signed_salt = 'signed encrypted cookie' key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) secret = key_generator.generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len] sign_secret = key_generator.generate_key(signed_salt) encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON) encryptor.decrypt_and_verify(cookie) end Pure Ruby Version: https://goo.gl/vuQPkr Improve Cookie-based Session with Decorator Pattern 49
slide-50
SLIDE 50

Encryption in Rack::Session::Cookie

Improve Cookie-based Session with Decorator Pattern 50
slide-51
SLIDE 51 require 'rack' require 'action_dispatch' secret_key_base = '...secret_key_base...' key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) app = lambda do |env| env['action_dispatch.secret_key_base'] = secret_key_base env['action_dispatch.cookies_serializer'] = :json env['action_dispatch.signed_cookie_salt'] = 'signed cookie' env['action_dispatch.encrypted_cookie_salt'] = 'encrypted cookie' env['action_dispatch.encrypted_signed_cookie_salt'] = 'signed encrypted cookie' env['action_dispatch.key_generator'] = key_generator session = env['rack.session'] session[:name] = 'tonytonyjan' session[:age] = 28 [200, {}, ['it works']] end app = Rack::Builder.app(app) do use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore, key: '_myapp_session' end Improve Cookie-based Session with Decorator Pattern 51
slide-52
SLIDE 52

Cons

  • ActionDispatch is fat.
  • Stack too deep.
Improve Cookie-based Session with Decorator Pattern 52
slide-53
SLIDE 53

Cons

  • ActionDispatch is fat.
  • Stack too deep.
Improve Cookie-based Session with Decorator Pattern 53
slide-54
SLIDE 54

Cons

  • ActionDispatch is fat.
  • Stack too deep.
Improve Cookie-based Session with Decorator Pattern 54
slide-55
SLIDE 55

Custom Coder

coder.respond_to?(:encode) # => true coder.respond_to?(:decode) # => true app = Rack::Builder.app(app) do use(Rack::Session::Cookie, coder: coder, let_coder_handle_secure_encoding: true ) end Improve Cookie-based Session with Decorator Pattern 55
slide-56
SLIDE 56

Custom Coder

class CustomCoder def encode(obj) Base64.encode64(Marshal.dump(obj)) end def decode(obj) Marshal.decode64(Base64.load(obj)) end end Improve Cookie-based Session with Decorator Pattern 56
slide-57
SLIDE 57

Cons

  • Lack of Flexibility
  • Tight coupling
Improve Cookie-based Session with Decorator Pattern 57
slide-58
SLIDE 58

Building Coder with Decorator Pattern

Improve Cookie-based Session with Decorator Pattern 58
slide-59
SLIDE 59 class Coder def initialize(coder = nil) @coder = coder end def encode(obj) raise NotImplementedError end def decode(obj) raise NotImplementedError end end Improve Cookie-based Session with Decorator Pattern 59
slide-60
SLIDE 60

Usage

class JsonCoder < Coder def encode(obj) JSON.dump(@coder.encode(obj)) end def decode(str) @coder.decode(JSON.parse(str)) end end Improve Cookie-based Session with Decorator Pattern 60
slide-61
SLIDE 61

Usage

class Base64Coder < Coder def encode(obj) Base64.encode64(@coder.encode(obj)) end def decode(str) @coder.decode(Base64.decode64(str)) end end Improve Cookie-based Session with Decorator Pattern 61
slide-62
SLIDE 62

A Coder Behaves like Rack::Session::Cookie

coder = HMACCoder.new( Base64Coder.new(MarshalCoder.new), secret: 'secret', digest: 'SHA1' ) Improve Cookie-based Session with Decorator Pattern 62
slide-63
SLIDE 63

What about Rails?

Improve Cookie-based Session with Decorator Pattern 63
slide-64
SLIDE 64

A Coder Behaves like Rails

Coders::HMAC.new( Coders::Cipher.new( Coders::JSON.new, secret: 'secret' ), secret: 'secret' ) Improve Cookie-based Session with Decorator Pattern 64
slide-65
SLIDE 65

gem 'coder_decorator'

Improve Cookie-based Session with Decorator Pattern 65
slide-66
SLIDE 66

Demo

Improve Cookie-based Session with Decorator Pattern 66
slide-67
SLIDE 67

Can they be part of Rack core?

Improve Cookie-based Session with Decorator Pattern 67
slide-68
SLIDE 68

Built-in coders in Rack

  • Rack::Session::Cookie::Base64::Marshal
  • Rack::Session::Cookie::Base64::JSON
  • Rack::Session::Cookie::Base64::ZipJSON
  • Rack::Session::Cookie::Identity
Improve Cookie-based Session with Decorator Pattern 68
slide-69
SLIDE 69

Bad Designs

  • They are implemented via inheritance
  • It repeats itself
  • Force to encode in base64 in the end
  • The namespace makes no sense
Improve Cookie-based Session with Decorator Pattern 69
slide-70
SLIDE 70

Bad Designs

  • They are implemented via inheritance
  • It repeats itself
  • Force to encode in base64 in the end
  • The namespace makes no sense
Improve Cookie-based Session with Decorator Pattern 70
slide-71
SLIDE 71 rack-2.0.4/lib/rack/session/cookie.rb class Base64 def encode(str); [str].pack('m') end def decode(str); str.unpack('m').first end class Marshal < Base64 def encode(str); super(::Marshal.dump(str)) end def decode(str) return unless str ::Marshal.load(super(str)) rescue nil end end end Improve Cookie-based Session with Decorator Pattern 71
slide-72
SLIDE 72

Bad Designs

  • They are implemented via inheritance
  • It repeats itself
  • Force to encode in base64 in the end
  • The namespace makes no sense
Improve Cookie-based Session with Decorator Pattern 72
slide-73
SLIDE 73 rack-2.0.4/lib/rack/session/cookie.rb class JSON < Base64 def encode(obj) super(::JSON.dump(obj)) end def decode(str) return unless str ::JSON.parse(super(str)) rescue nil end end Improve Cookie-based Session with Decorator Pattern 73
slide-74
SLIDE 74 rack-2.0.4/lib/rack/session/cookie.rb class ZipJSON < Base64 def encode(obj) super(Zlib::Deflate.deflate(::JSON.dump(obj))) end def decode(str) return unless str ::JSON.parse(Zlib::Inflate.inflate(super(str))) rescue nil end end Improve Cookie-based Session with Decorator Pattern 74
slide-75
SLIDE 75

Bad Designs

  • They are implemented via inheritance
  • It repeats itself
  • Force to encode in base64 in the end
  • The namespace makes no sense
Improve Cookie-based Session with Decorator Pattern 75
slide-76
SLIDE 76 rack-2.0.4/lib/rack/session/cookie.rb class Base64 class Marshal < Base64 end class JSON < Base64 end class ZipJSON < Base64 end end Improve Cookie-based Session with Decorator Pattern 76
slide-77
SLIDE 77

Bad Designs

  • They are implemented via inheritance
  • It repeats itself
  • Force to encode in base64 in the end
  • The namespace makes no sense
Improve Cookie-based Session with Decorator Pattern 77
slide-78
SLIDE 78

Built-in coders in Rack

  • Rack::Session::Cookie::Base64::Marshal
  • Rack::Session::Cookie::Base64::JSON
  • Rack::Session::Cookie::Base64::ZipJSON
  • Rack::Session::Cookie::Identity
Improve Cookie-based Session with Decorator Pattern 78
slide-79
SLIDE 79

A Better Namespace to Put Coders

  • Rack::Cokers::Coder
  • Rack::Cokers::JSON
  • Rack::Cokers::Marshal
  • Rack::Cokers::HMAC
Improve Cookie-based Session with Decorator Pattern 79
slide-80
SLIDE 80

Improve Rack!

Improve Cookie-based Session with Decorator Pattern 80
slide-81
SLIDE 81

Difficulty

  • Backward compatibility
  • Signature is not controlled by coder
Improve Cookie-based Session with Decorator Pattern 81
slide-82
SLIDE 82

Difficulty

  • Backward compatibility
  • Signature is not controlled by coder
Improve Cookie-based Session with Decorator Pattern 82
slide-83
SLIDE 83

Difficulty

  • Backward compatibility
  • Signature is not controlled by coder
Improve Cookie-based Session with Decorator Pattern 83
slide-84
SLIDE 84 rack-2.0.4/lib/rack/session/cookie.rb module Rack::Session::Cookie def initialize @secrets = options.values_at(:secret, :old_secret).compact end def write_session(req, session_id, session, options) if @secrets.first session_data << "--#{generate_hmac(session_data, @secrets.first)}" end end end Improve Cookie-based Session with Decorator Pattern 84
slide-85
SLIDE 85 https://github.com/rack/rack/pull/1134 Improve Cookie-based Session with Decorator Pattern 85
slide-86
SLIDE 86

Conclusion

  • source codes never lie
  • mature project can still have some bad design
Improve Cookie-based Session with Decorator Pattern 86
slide-87
SLIDE 87

Happy Coding

Improve Cookie-based Session with Decorator Pattern 87
slide-88
SLIDE 88

THANKS FOR YOUR LISTENING

@tonytonyjan

Improve Cookie-based Session with Decorator Pattern 88
slide-89
SLIDE 89

References

  • https://www.wikiwand.com/en/Design_Patterns
  • https://github.com/tonytonyjan/coder_decorator/
  • https://gist.github.com/tonytonyjan/
d71f040fe1085dfcf1d4#file-rails5withpureruby-rb
  • https://github.com/rack/rack/pull/1134
  • https://github.com/sinatra/sinatra.github.com/pull/220
  • https://github.com/rubymonsters/webapps-for-beginners/
Improve Cookie-based Session with Decorator Pattern 89
slide-90
SLIDE 90

https://tonytonyjan.net/slides

Improve Cookie-based Session with Decorator Pattern 90
slide-91
SLIDE 91

Q&A

Improve Cookie-based Session with Decorator Pattern 91