stoor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []