Class: Rack::WWWhisper

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/wwwhisper.rb

Overview

Communicates with the wwwhisper service to authorize each incoming request. Acts as a proxy for requests to locations handled by wwwhisper (/wwwhisper/auth and /wwwhisper/admin)

For each incoming request an authorization query is sent. The query contains a normalized path that a request is trying to access and wwwhisper session cookies. The query result determines the action to be performed:

200

request is allowed and passed down the Rack stack.

401

the user is not authenticated, request is denied, login page is returned.

403

the user is not authorized, request is denied, error is returned.

any other

error while communicating with wwwhisper, request is denied.

This class is thread safe, it can handle multiple simultaneous requests.

Defined Under Namespace

Classes: NoPublicCache

Constant Summary collapse

@@WWWHISPER_PREFIX =

Path prefix of requests that are passed to wwwhisper.

'/wwwhisper/'
@@AUTH_COOKIES_PREFIX =

Name prefix of cookies that are passed to wwwhisper.

'wwwhisper'
@@FORWARDED_HEADERS =

Headers that are passed to wwwhisper (‘Cookie’ is handled in a special way: only wwwhisper related cookies are passed).

In addition, the current site URL is passed in the Site-Url header. This is needed to disallow access with access tokens generated for other sites and to construct Location headers in redirects.

wwwhisper library version is passed in the User-Agent header. This is to warn the site owner if a vulnerability in the library is discovered and the library needs to be updated.

['Accept', 'Accept-Language', 'Cookie', 'Origin',
'X-CSRFToken', 'X-Requested-With']
@@DEFAULT_IFRAME =
%Q[<script type="text/javascript" src="%s"> </script>
]

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ WWWhisper

Following environment variables are recognized:

  1. WWWHISPER_DISABLE: useful for a local development environment.

  2. WWWHISPER_URL: an address of a wwwhisper service that must be set if WWWHISPER_DISABLE is not set. The url includes credentials that identify a protected site. If the same credentials are used for www.example.org and www.example.com, the sites are treated as one: access control rules defined for one site, apply to the other site.

  3. WWWHISPER_IFRAME: an HTML snippet to be injected into returned HTML documents (has a default value).



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/rack/wwwhisper.rb', line 63

def initialize(app)
  @app = app
  if not ENV['WWWHISPER_URL']
    def self.call(env)
      # Delay check for WWWHISPER_DISABLE until the first
      # request. This way Rails assets pipeline does not fail if
      # environment variables are not set (as is the case on
      # Heroku).
      if ENV['WWWHISPER_DISABLE'] != '1'
        raise(StandardError,
              'WWWHISPER_URL nor WWWHISPER_DISABLE environment variable set')
      end
      @app.call(env)
    end
    return
  end

  @app = NoPublicCache.new(app)

  # net/http/persistent connections are thread safe.
  @http = http_init()
  @wwwhisper_uri = parse_uri(ENV['WWWHISPER_URL'])

  @wwwhisper_iframe = ENV['WWWHISPER_IFRAME'] ||
    sprintf(@@DEFAULT_IFRAME, wwwhisper_path('auth/iframe.js'))
  @wwwhisper_iframe_bytesize = @wwwhisper_iframe.bytesize
end

Instance Method Details

#auth_query(queried_path) ⇒ Object

Exposed for tests.



97
98
99
# File 'lib/rack/wwwhisper.rb', line 97

def auth_query(queried_path)
  wwwhisper_path "auth/api/is-authorized/?path=#{queried_path}"
end

#call(env) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/rack/wwwhisper.rb', line 101

def call(env)
  req = Request.new(env)
  normalize_path(req)

  # Requests to /@@WWWHISPER_PREFIX/auth/ should not be authorized,
  # every visitor can access login pages.
  return dispatch(req) if req.path =~ %r{^#{wwwhisper_path('auth')}}

  debug req, "sending auth request for #{req.path}"
  auth_resp = auth_request(req)

  if auth_resp.code == '200'
    debug req, 'access granted'
    user = auth_resp['User']
    env['REMOTE_USER'] = user if user
    status, headers, body = dispatch(req)
    if should_inject_iframe(status, headers)
      body = inject_iframe(headers, body)
    end
    headers['User'] = user if user
    [status, headers, body]
  else
    debug req, {
      '401' => 'user not authenticated',
      '403' => 'access_denied',
    }[auth_resp.code] || 'auth request failed'
    sub_response_to_rack(req, auth_resp)
  end
end

#wwwhisper_path(suffix) ⇒ Object

Exposed for tests.



92
93
94
# File 'lib/rack/wwwhisper.rb', line 92

def wwwhisper_path(suffix)
  "#{@@WWWHISPER_PREFIX}#{suffix}"
end