stoor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ coverage
2
+ .bundle
3
+ pkg
4
+ .DS_Store
5
+ Gemfile.lock
6
+ *.gem
7
+ vendor
8
+ .ruby-version
9
+ log/*.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,130 @@
1
+ This is an example of fronting Gollum with a small Sinatra app that authorizes against GitHub.
2
+
3
+ ## Rationale
4
+
5
+ In our environment, the contents of our wiki are confidential and should not be pushed up to GitHub. We keep the wiki on
6
+ a local machine (behind a firewall), and put the wiki contents into a directory that is sync'd to box.com. (box.com is useful for confidential
7
+ data, because they will sign a HIPAA BAA.) Meanwhile, we'd like to authorize access by some means, so we use GitHub Oauth,
8
+ so that we can constrain access by GitHub Organization Team membership.
9
+
10
+ ## Requirements
11
+
12
+ Ruby 1.9.2 or greater.
13
+
14
+ Unfortunately, this will no longer work on Ruby 1.8.7, because `gollum-lib` now wants Nokogiri 1.6.0 ([see?](https://github.com/gollum/gollum-lib/commit/eeb0a4a036001c7621d173e7152b91ed02b21ed0#commitcomment-4170065)), and
15
+ 1.8.7 isn't supported. That's too bad, because it was nice that this would work on the system Ruby on a Mac.
16
+
17
+ ## Setup
18
+
19
+ gem install stoor
20
+
21
+ ## Usage examples
22
+
23
+ (Relax, the client id and secret below are fake.)
24
+
25
+ ### The 'stoor' command
26
+
27
+ To get started, type:
28
+
29
+ stoor
30
+
31
+ This will run your gollum wiki on port 3000, though it will decorate the footer with a message saying who
32
+ the committer is. When not authenticating against GitHub, the default options for the wiki repo is used (i.e.,
33
+ the values for the GitHub commit will be what you see in `git config -l`).
34
+
35
+ The `stoor` command is a thin wrapper around the `thin` web server, and takes all `thin` options (`-p <port>`, etc.).
36
+
37
+ ### Specify the Wiki repo location
38
+
39
+ WIKI_PATH=/Users/admin/wiki stoor
40
+
41
+ The `WIKI_PATH` environment variable provides for locating the wiki contents in a differet repo from the
42
+ Stoor application. It is strongly advised that you do this so that you can keep your wiki code and wiki
43
+ content separate.
44
+
45
+ ### GitHub authorization
46
+
47
+ Require authorization via GitHub to the GitHub application with the given client id and secret
48
+
49
+ GITHUB_CLIENT_ID=780ec06a331b4f61a345 GITHUB_CLIENT_SECRET=f1e5439aff166c34f707747120acbf66ef233fc2 stoor
50
+
51
+ Access to the wiki will first run through GitHub OAuth against the app specified by the id and secret. For information
52
+ on setting up an application in GitHub and obtaining its id and secret, see <https://github.com/settings/applications/new>.
53
+ If you are running Stoor on localhost with Rackup, the typical settings would be:
54
+
55
+ Application Name | Main URL | Callback URL
56
+ --- | --- |
57
+ YourAppName | http://localhost:3000 | http://localhost:3000/auth/github/callback
58
+
59
+ **NOTE:** No matter what your domain and port, the callback path must be `/auth/github/callback`.
60
+
61
+ **NOTE:** See also `STOOR_DOMAIN` below: The domain specified for the cookie should match the domain in your GitHub
62
+ application settings.
63
+
64
+ ### Prefer a certain email domain
65
+
66
+ If there is more than one email associated with the GitHub user, prefer the one from the specified domain (otherwise the first email will be used)
67
+
68
+ GITHUB_EMAIL_DOMAIN=7fff.com GITHUB_CLIENT_ID=780ec06a331b4f61a345 GITHUB_CLIENT_SECRET=f1e5439aff166c34f707747120acbf66ef233fc2 stoor
69
+
70
+ ### Require GitHub team
71
+
72
+ GITHUB_TEAM_ID=11155 GITHUB_CLIENT_ID=780ec06a331b4f61a345 GITHUB_CLIENT_SECRET=f1e5439aff166c34f707747120acbf66ef233fc2 stoor
73
+
74
+ If the user is not a member of the specified team, they aren't allowed access.
75
+
76
+ ### Specify the domain (this is to ensure that cookies are set for the correct domain)
77
+
78
+ STOOR_DOMAIN=wiki.local # default: localhost
79
+
80
+ ### Specify the cookie secret
81
+
82
+ STOOR_SECRET="honi soit qui mal y pense" # default: stoor
83
+
84
+ ### Specify the cookie timeout
85
+
86
+ STOOR_EXPIRE_AFTER=600 # In seconds; default: 3600
87
+
88
+ ### Wide display
89
+
90
+ STOOR_WIDE=y # Main wiki content will take 90% of browser width; widens tables as well
91
+
92
+ ## How I run it
93
+
94
+ I like having my own personal wiki. Since Apache is ubiquitous on Macs, I run the Wiki with configuration in `/etc/apache2/httpd.conf`,
95
+ ~~~and just use my system Ruby~~~ some Ruby provided by rbenv, and Passenger.
96
+
97
+ I create an extra name for 127.0.0.1 in `/etc/hosts` such as `wiki.local`. Then:
98
+
99
+ gem install passenger
100
+ passenger-install-apache2-module
101
+
102
+ Then in `/etc/apache2/httpd.conf`:
103
+
104
+ LoadModule passenger_module /opt/boxen/rbenv/versions/1.9.2-p320/lib/ruby/gems/1.9.1/gems/passenger-4.0.19/buildout/apache2/mod_passenger.so
105
+ PassengerRoot /opt/boxen/rbenv/versions/1.9.2-p320/lib/ruby/gems/1.9.1/gems/passenger-4.0.19
106
+ PassengerDefaultRuby /opt/boxen/rbenv/versions/1.9.2-p320/bin/ruby
107
+
108
+ NameVirtualHost *:80
109
+
110
+ <VirtualHost *:80>
111
+ SetEnv GITHUB_CLIENT_ID 780ec06a331b4f61a345
112
+ SetEnv GITHUB_CLIENT_SECRET f1e5439aff166c34f707747120acbf66ef233fc2
113
+ SetEnv GITHUB_EMAIL_DOMAIN 7fff.com
114
+ SetEnv STOOR_DOMAIN wiki.local
115
+ SetEnv STOOR_EXPIRE_AFTER 60
116
+ SetEnv WIKI_PATH /Users/jgn/Dropbox/wiki
117
+ ServerName wiki.local
118
+ DocumentRoot "/opt/boxen/rbenv/versions/1.9.2-p320/lib/ruby/gems/1.9.1/gems/stoor-0.0.1/public"
119
+ <Directory "/opt/boxen/rbenv/versions/1.9.2-p320/lib/ruby/gems/1.9.1/gems/stoor-0.0.1/public">
120
+ Allow from all
121
+ Options -MultiViews
122
+ </Directory>
123
+ </VirtualHost>
124
+
125
+ and finally:
126
+
127
+ sudo apachectl -k restart
128
+
129
+ Now browse your wiki at <http://wiki.local>
130
+
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thin'
4
+ ARGV.unshift 'config.ru'
5
+ ARGV.unshift '-R'
6
+ ARGV.unshift 'start'
7
+ Thin::Runner.new(ARGV).run!
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
4
+ require 'rubygems'
5
+ require 'logger'
6
+ require 'bundler/setup'
7
+ require 'sinatra_auth_github'
8
+ require 'gollum/app'
9
+ require 'stoor'
10
+
11
+ # Force the NullLogger to be a no-op, since it keeps getting bound into the
12
+ # Request instance.
13
+ module Rack
14
+ class NullLogger
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def call(env)
20
+ @app.call(env)
21
+ end
22
+ end
23
+ end
24
+
25
+ ENV['RACK_ENV'] ||= 'development'
26
+ log_frag = "#{File.dirname(__FILE__)}/log/#{ENV['RACK_ENV']}"
27
+ access_logger = Logger.new("#{log_frag}_access.log")
28
+ access_logger.instance_eval do
29
+ def write(msg); self.send(:<<, msg); end
30
+ end
31
+ access_logger.level = Logger::INFO
32
+ log_stream = File.open("#{log_frag}.log", 'a+')
33
+ log_stream.sync = true
34
+
35
+ domain = ENV['STOOR_DOMAIN'] || 'localhost'
36
+ secret = ENV['STOOR_SECRET'] || 'stoor'
37
+ expire_after = (ENV['STOOR_EXPIRE_AFTER'] || '3600').to_i
38
+
39
+ wiki_path = ENV['WIKI_PATH_IN_USE'] = ENV['WIKI_PATH'] || File.expand_path(File.dirname(__FILE__))
40
+
41
+ use Rack::CommonLogger, access_logger
42
+ use Stoor::Logger, log_stream, Logger::INFO
43
+ use Rack::Session::Cookie, :domain => domain, :key => 'rack.session', :secret => secret, :expire_after => expire_after
44
+
45
+ use Stoor::GithubAuth
46
+ use Stoor::GitConfig
47
+ use Stoor::Decorate
48
+ use Stoor::FixCssWidth if ENV['STOOR_WIDE']
49
+
50
+ Precious::App.set(:gollum_path, wiki_path)
51
+ Precious::App.set(:default_markup, :markdown)
52
+ Precious::App.set(:wiki_options, { :universal_toc =>false })
53
+ run Precious::App
@@ -0,0 +1,18 @@
1
+ require 'stoor/logger'
2
+ require 'stoor/github_auth'
3
+ require 'stoor/git_config'
4
+ require 'stoor/decorate'
5
+ require 'stoor/fix_css_width'
6
+ require 'stoor/views/layout'
7
+ require 'stoor/views/logout'
8
+
9
+ # In at least gollum 2.4.13 and later:
10
+ # https://github.com/gollum/gollum/blob/master/lib/gollum/uri_encode_component.rb#L36
11
+ # seems to get scoped funny, and I see this:
12
+ # NoMethodError - undefined method `URIEncodeComponent' for #<URI::Parser:0x007f91db50bd78>:
13
+ # This fixes it.
14
+ if RUBY_VERSION == '1.9.2'
15
+ def encodeURIComponent(componentString)
16
+ ::URI::URIEncodeComponent(componentString)
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ module Stoor
2
+ class Decorate
3
+ include Rack::Utils
4
+
5
+ FOOTER_REGEXP = /(<div id="footer">)(.*?)(<\/div>)/im
6
+
7
+ def initialize(app); @app = app; end
8
+
9
+ def call(env)
10
+ @request = Rack::Request.new(env)
11
+
12
+ if @request.session['gollum.author'].nil?
13
+ @request.logger.info "No 'gollum.author' in session - skipping page decoration."
14
+ return @app.call(env)
15
+ end
16
+
17
+ status, headers, response = @app.call(env)
18
+ headers = HeaderHash.new(headers)
19
+
20
+ if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
21
+ !headers['transfer-encoding'] &&
22
+ headers['content-type'] &&
23
+ headers['content-type'].include?("text/html")
24
+
25
+ # TODO: If the response isn't an Array, it's a Rack::File or something, so ignore
26
+ if response.respond_to? :inject
27
+ content = response.inject("") { |content, part| content << part }
28
+ if match_data = content.match(FOOTER_REGEXP)
29
+ new_body = "" <<
30
+ match_data.pre_match <<
31
+ match_data[1] <<
32
+ before_existing_footer <<
33
+ match_data[2] <<
34
+ after_existing_footer <<
35
+ match_data[3] <<
36
+ match_data.post_match
37
+ headers['Content-Length'] = new_body.bytesize.to_s
38
+ return [status, headers, [new_body]]
39
+ end
40
+ end
41
+ end
42
+
43
+ [status, headers, response]
44
+ end
45
+
46
+ def before_existing_footer
47
+ <<-HTML
48
+ <div style="float: left;">
49
+ HTML
50
+ end
51
+
52
+ def after_existing_footer
53
+ <<-HTML
54
+ </div>
55
+ <div style="float: right;">
56
+ <p style="text-align: right; font-size: .9em; line-height: 1.6em; color: #999; margin: 0.9em 0;">
57
+ Commiting as <b>#{@request.session['gollum.author'][:name]}</b> (#{@request.session['gollum.author'][:email]})#{" | <a href='/logout'>Logout</a>" if ENV['GITHUB_AUTHORIZED']}
58
+ </p>
59
+ </div>
60
+ HTML
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ module Stoor
2
+ class FixCssWidth
3
+ include Rack::Utils
4
+
5
+ ADDITIONAL_STYLES = '<style type="text/css">#wiki-wrapper { width: 90%; } .markdown-body table { width: 100%; }</style>'
6
+
7
+ def initialize(app); @app = app; end
8
+
9
+ def call(env)
10
+ @request = Rack::Request.new(env)
11
+
12
+ status, headers, response = @app.call(env)
13
+ headers = HeaderHash.new(headers)
14
+
15
+ if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
16
+ !headers['transfer-encoding'] &&
17
+ headers['content-type'] &&
18
+ headers['content-type'].include?("text/html")
19
+
20
+ # TODO: If the response isn't an Array, it's a Rack::File or something, so ignore
21
+ if response.respond_to? :inject
22
+ content = response.inject("") { |content, part| content << part }
23
+ if content =~ /<body>/
24
+ pre, match, post = $`, $&, $'
25
+ new_body = pre + match + ADDITIONAL_STYLES + post
26
+ headers['Content-Length'] = new_body.bytesize.to_s
27
+ return [status, headers, [new_body]]
28
+ end
29
+ end
30
+ end
31
+
32
+ [status, headers, response]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module Stoor
2
+ class GitConfig
3
+ def initialize(app); @app = app; end
4
+
5
+ def call(env)
6
+ @request = Rack::Request.new(env)
7
+ unless @request.session['gollum.author']
8
+ if ENV['WIKI_PATH_IN_USE']
9
+ if name = git_option_value('user.name')
10
+ if email = git_option_value('user.email')
11
+ @request.session['gollum.author'] = { :name => name, :email => email }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ @app.call(env)
17
+ end
18
+
19
+ def git_option_value(option)
20
+ `cd #{ENV['WIKI_PATH_IN_USE']} && git config --get #{option}`.strip
21
+ rescue
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module Stoor
2
+ class GithubAuth < Sinatra::Base
3
+
4
+ set :github_options, {
5
+ :scopes => "user,user:email",
6
+ :client_id => ENV['GITHUB_CLIENT_ID'],
7
+ :secret => ENV['GITHUB_CLIENT_SECRET']
8
+ }
9
+
10
+ register Sinatra::Auth::Github
11
+ register Mustache::Sinatra
12
+
13
+ get '/logout' do
14
+ logout!
15
+ mustache :logout
16
+ end
17
+
18
+ get '/*' do
19
+ ENV['GITHUB_AUTHORIZED'] = nil
20
+
21
+ pass unless ENV['GITHUB_CLIENT_ID'] && ENV['GITHUB_CLIENT_SECRET']
22
+
23
+ pass if request.path_info =~ /\./
24
+
25
+ authenticate!
26
+ if ENV['GITHUB_TEAM_ID']
27
+ github_team_authenticate!(ENV['GITHUB_TEAM_ID'])
28
+ end
29
+
30
+ ENV['GITHUB_AUTHORIZED'] = "yes"
31
+
32
+ email = nil
33
+ emails = github_user.api.emails
34
+ if ENV['GITHUB_EMAIL_DOMAIN']
35
+ email = emails.find { |e| e =~ /#{ENV['GITHUB_EMAIL_DOMAIN']}/ }
36
+ end
37
+ email ||= emails.first
38
+ session['gollum.author'] = { :name => github_user.name, :email => email }
39
+ pass
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # Like Rack::Logger, but provide for defining the stream
2
+ # at initialization.
3
+ module Stoor
4
+ class Logger
5
+ def initialize(app, stream = nil, level = ::Logger::INFO)
6
+ @app, @stream, @level = app, stream, level
7
+ end
8
+
9
+ def call(env)
10
+ stream = @stream || env['rack.errors']
11
+ logger = ::Logger.new(stream)
12
+ logger.level = @level
13
+
14
+ env['rack.logger'] = logger
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Stoor
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,10 @@
1
+ class Stoor::GithubAuth
2
+ module Views
3
+ class Layout < Precious::Views::Layout
4
+ def self.template_file
5
+ # Steal Gollum's layout so we get the CSS, JavaScript, etc.
6
+ @template_file ||= File.join(File.dirname(Precious::App.new!.method(:wiki_page).source_location[0]), 'templates', 'layout.mustache')
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ <div id="wiki-wrapper" class="page">
2
+ <div class="markdown-body">
3
+ <p>You're logged out; <a href="/">Log back in</a>.</p>
4
+ </div>
5
+ </div>
@@ -0,0 +1,9 @@
1
+ class Stoor::GithubAuth
2
+ module Views
3
+ class Logout < Layout
4
+ def self.template_file
5
+ @template_file ||= File.join(File.dirname(__FILE__), 'logout.mustache')
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ !.gitignore
@@ -0,0 +1 @@
1
+ !.gitignore
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.join('..', 'lib'), __FILE__)
2
+ require 'stoor/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'stoor'
6
+ s.version = Stoor::VERSION
7
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
8
+ s.summary = 'Front-end for Gollum'
9
+ s.description = 'Front-end for Gollum'
10
+ s.authors = ['John G. Norman']
11
+ s.email = 'john@7fff.com'
12
+ s.files = `git ls-files`.split("\n")
13
+ s.homepage = 'https://rubygems.org/gems/stoor'
14
+ s.rdoc_options = ['--charset=UTF-8']
15
+ s.require_paths = ['lib']
16
+ s.test_files = `git ls-files spec`.split("\n")
17
+ s.add_dependency 'thin', '~> 1.5.1'
18
+ s.add_dependency 'gollum', '~> 2.5.0'
19
+ s.add_dependency 'sinatra_auth_github', '~> 1.0.0'
20
+ s.add_dependency 'json', '~> 1.8.0'
21
+ s.executables << 'stoor'
22
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stoor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John G. Norman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thin
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: gollum
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.5.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.5.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: sinatra_auth_github
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: json
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.8.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.0
78
+ description: Front-end for Gollum
79
+ email: john@7fff.com
80
+ executables:
81
+ - stoor
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - README.md
88
+ - bin/stoor
89
+ - config.ru
90
+ - lib/stoor.rb
91
+ - lib/stoor/decorate.rb
92
+ - lib/stoor/fix_css_width.rb
93
+ - lib/stoor/git_config.rb
94
+ - lib/stoor/github_auth.rb
95
+ - lib/stoor/logger.rb
96
+ - lib/stoor/version.rb
97
+ - lib/stoor/views/layout.rb
98
+ - lib/stoor/views/logout.mustache
99
+ - lib/stoor/views/logout.rb
100
+ - log/.gitignore
101
+ - public/.gitignore
102
+ - stoor.gemspec
103
+ homepage: https://rubygems.org/gems/stoor
104
+ licenses: []
105
+ post_install_message:
106
+ rdoc_options:
107
+ - --charset=UTF-8
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 1.8.23
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Front-end for Gollum
128
+ test_files: []