jekyll-auth 0.6.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +15 -0
- data/Gemfile +3 -0
- data/README.md +149 -0
- data/Rakefile +18 -7
- data/bin/jekyll-auth +97 -109
- data/jekyll-auth.gemspec +31 -0
- data/lib/jekyll-auth.rb +31 -6
- data/lib/jekyll_auth/auth_site.rb +47 -0
- data/lib/jekyll_auth/commands.rb +73 -0
- data/lib/jekyll_auth/config_error.rb +8 -0
- data/lib/jekyll_auth/helpers.rb +18 -0
- data/lib/jekyll_auth/jekyll_site.rb +14 -0
- data/lib/jekyll_auth/sinatra/auth/github.rb +11 -0
- data/lib/{jekyll-auth → jekyll_auth}/version.rb +1 -1
- data/script/bootstrap +7 -0
- data/script/cibuild +8 -0
- data/script/console +1 -0
- data/script/release +38 -0
- data/script/server +3 -0
- data/script/setup +5 -0
- data/spec/jekyll_auth_auth_site_spec.rb +76 -0
- data/spec/jekyll_auth_bin_spec.rb +44 -0
- data/spec/jekyll_auth_commands_spec.rb +76 -0
- data/spec/jekyll_auth_helpers_spec.rb +62 -0
- data/spec/jekyll_auth_jekyll_site_spec.rb +44 -0
- data/spec/jekyll_auth_spec.rb +47 -0
- data/spec/spec_helper.rb +60 -0
- data/templates/.gitignore +3 -0
- data/templates/Rakefile +9 -0
- data/{config.ru → templates/config.ru} +0 -0
- data/templates/index.html +19 -0
- metadata +126 -25
- data/lib/jekyll-auth/auth-site.rb +0 -47
- data/lib/jekyll-auth/config.rb +0 -28
- data/lib/jekyll-auth/jekyll-site.rb +0 -7
data/jekyll-auth.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require './lib/jekyll_auth/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "jekyll-auth"
|
5
|
+
s.version = JekyllAuth::VERSION
|
6
|
+
s.summary = "A simple way to use Github OAuth to serve a protected jekyll site to your GitHub organization"
|
7
|
+
s.description = "A simple way to use Github Oauth to serve a protected jekyll site to your GitHub organization."
|
8
|
+
s.authors = "Ben Balter"
|
9
|
+
s.email = "ben@balter.com"
|
10
|
+
s.homepage = "https://github.com/benbalter/jekyll-auth"
|
11
|
+
s.license = "MIT"
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.add_dependency "jekyll", "~> 2.0"
|
18
|
+
s.add_dependency "sinatra-index", "~> 0.0"
|
19
|
+
s.add_dependency "sinatra_auth_github", "~> 1.1"
|
20
|
+
s.add_dependency "rack", "1.5.2"
|
21
|
+
s.add_dependency "dotenv", "~> 1.0"
|
22
|
+
s.add_dependency "rake", "~> 10.3"
|
23
|
+
s.add_dependency "rack-ssl-enforcer", "~> 0.2"
|
24
|
+
s.add_dependency "mercenary", "~> 0.3"
|
25
|
+
s.add_dependency 'safe_yaml', "~> 1.0"
|
26
|
+
s.add_dependency "colorator", "~> 0.1"
|
27
|
+
s.add_development_dependency "rspec", "~> 3.1"
|
28
|
+
s.add_development_dependency "rack-test", "~> 0.6"
|
29
|
+
s.add_development_dependency "webmock", "~> 1.2 "
|
30
|
+
s.add_development_dependency "pry", "~> 0.10"
|
31
|
+
end
|
data/lib/jekyll-auth.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'sinatra-index'
|
3
2
|
require 'sinatra_auth_github'
|
4
|
-
require 'rack'
|
5
3
|
require 'dotenv'
|
6
4
|
require 'safe_yaml'
|
7
|
-
require
|
8
|
-
require
|
9
|
-
|
10
|
-
|
5
|
+
require 'colorator'
|
6
|
+
require 'mkmf'
|
7
|
+
require_relative 'jekyll_auth/version'
|
8
|
+
require_relative 'jekyll_auth/helpers'
|
9
|
+
require_relative 'jekyll_auth/auth_site'
|
10
|
+
require_relative 'jekyll_auth/jekyll_site'
|
11
|
+
require_relative 'jekyll_auth/config_error'
|
12
|
+
require_relative 'jekyll_auth/commands'
|
13
|
+
|
11
14
|
Dotenv.load
|
12
15
|
|
13
16
|
class JekyllAuth
|
@@ -17,4 +20,26 @@ class JekyllAuth
|
|
17
20
|
run JekyllAuth::JekyllSite
|
18
21
|
end
|
19
22
|
end
|
23
|
+
|
24
|
+
def self.config_file
|
25
|
+
File.join(Dir.pwd, "_config.yml")
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.config
|
29
|
+
@config ||= begin
|
30
|
+
config = YAML.safe_load_file(config_file)
|
31
|
+
config["jekyll_auth"] || {}
|
32
|
+
rescue
|
33
|
+
{}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.whitelist
|
38
|
+
whitelist = JekyllAuth::config["whitelist"]
|
39
|
+
Regexp.new(whitelist.join("|")) unless whitelist.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.ssl?
|
43
|
+
!!JekyllAuth::config["ssl"]
|
44
|
+
end
|
20
45
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class JekyllAuth
|
2
|
+
class AuthSite < Sinatra::Base
|
3
|
+
|
4
|
+
configure :production do
|
5
|
+
require 'rack-ssl-enforcer'
|
6
|
+
use Rack::SslEnforcer if JekyllAuth.ssl?
|
7
|
+
end
|
8
|
+
|
9
|
+
use Rack::Session::Cookie, {
|
10
|
+
:http_only => true,
|
11
|
+
:secret => ENV['SESSION_SECRET'] || SecureRandom.hex
|
12
|
+
}
|
13
|
+
|
14
|
+
set :github_options, {
|
15
|
+
:scopes => 'read:org'
|
16
|
+
}
|
17
|
+
|
18
|
+
ENV['WARDEN_GITHUB_VERIFIER_SECRET'] ||= SecureRandom.hex
|
19
|
+
register Sinatra::Auth::Github
|
20
|
+
|
21
|
+
use Rack::Logger
|
22
|
+
|
23
|
+
include JekyllAuth::Helpers
|
24
|
+
|
25
|
+
before do
|
26
|
+
pass if whitelisted?
|
27
|
+
|
28
|
+
logger.info "Authentication strategy: #{authentication_strategy}"
|
29
|
+
|
30
|
+
case authentication_strategy
|
31
|
+
when :team
|
32
|
+
github_team_authenticate! ENV['GITHUB_TEAM_ID']
|
33
|
+
when :teams
|
34
|
+
github_teams_authenticate! ENV['GITHUB_TEAM_IDS'].split(",")
|
35
|
+
when :org
|
36
|
+
github_organization_authenticate! ENV['GITHUB_ORG_ID']
|
37
|
+
else
|
38
|
+
raise JekyllAuth::ConfigError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
get '/logout' do
|
43
|
+
logout!
|
44
|
+
redirect '/'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
class JekyllAuth
|
2
|
+
class Commands
|
3
|
+
|
4
|
+
FILES = %w{Rakefile config.ru .gitignore .env}
|
5
|
+
VARS = %w{client_id client_secret team_id org_id}
|
6
|
+
|
7
|
+
def self.source
|
8
|
+
@source ||= File.expand_path( "../../templates", File.dirname(__FILE__) )
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.destination
|
12
|
+
@destination ||= Dir.pwd
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.changed?
|
16
|
+
execute_command("git", "status", destination, "--porcelain").length != 0
|
17
|
+
rescue
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.execute_command(*args)
|
22
|
+
output, status = Open3.capture2e(*args)
|
23
|
+
raise "Command `#{args.join(" ")}` failed: #{output}" if status != 0
|
24
|
+
output
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.copy_templates
|
28
|
+
FILES.each do |file|
|
29
|
+
if File.exist? "#{destination}/#{file}"
|
30
|
+
puts "* #{destination}/#{file} already exists... skipping."
|
31
|
+
else
|
32
|
+
puts "* creating #{destination}/#{file}"
|
33
|
+
FileUtils.cp "#{source}/#{file}", "#{destination}/#{file}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.team_id(org, team)
|
39
|
+
client = Octokit::Client.new :access_token => ENV["GITHUB_TOKEN"]
|
40
|
+
client.auto_paginate = true
|
41
|
+
teams = client.organization_teams org
|
42
|
+
found = teams.find { |t| t[:slug] == team }
|
43
|
+
found[:id] if found
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.env_var_set?(var)
|
47
|
+
!(ENV[var].to_s.blank?)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.init_repo
|
51
|
+
execute_command "git", "init", destination
|
52
|
+
FILES.each do |file|
|
53
|
+
next if file == ".env"
|
54
|
+
execute_command("git", "add", "--", "#{destination}/#{file}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.initial_commit
|
59
|
+
execute_command "git", "commit", "-m", "'[Jekyll Auth] Initial setup'"
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.heroku_remote_set?
|
63
|
+
remotes = execute_command "git", "remote", "-v"
|
64
|
+
!!(remotes =~ /^heroku\s/)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.configure_heroku(options)
|
68
|
+
VARS.each do |var|
|
69
|
+
execute_command "heroku", "config:set", "GITHUB_#{var.upcase}=#{options[var]}" if options[var]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class JekyllAuth
|
2
|
+
module Helpers
|
3
|
+
def whitelisted?
|
4
|
+
return true if request.path_info == "/logout"
|
5
|
+
!!(JekyllAuth.whitelist && JekyllAuth.whitelist.match(request.path_info))
|
6
|
+
end
|
7
|
+
|
8
|
+
def authentication_strategy
|
9
|
+
if !ENV['GITHUB_TEAM_ID'].to_s.blank?
|
10
|
+
:team
|
11
|
+
elsif !ENV['GITHUB_TEAM_IDS'].to_s.blank?
|
12
|
+
:teams
|
13
|
+
elsif !ENV['GITHUB_ORG_ID'].to_s.blank?
|
14
|
+
:org
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class JekyllAuth
|
2
|
+
class JekyllSite < Sinatra::Base
|
3
|
+
|
4
|
+
register Sinatra::Index
|
5
|
+
set :public_folder, File.expand_path('_site', Dir.pwd)
|
6
|
+
use_static_index 'index.html'
|
7
|
+
|
8
|
+
not_found do
|
9
|
+
status 404
|
10
|
+
four_oh_four = File.expand_path('_site/404.html', Dir.pwd)
|
11
|
+
File.read(four_oh_four) if File.exists?(four_oh_four)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Auth
|
3
|
+
module Github
|
4
|
+
# Like the native github_team_authenticate! but accepts an array of team ids
|
5
|
+
def github_teams_authenticate!(teams)
|
6
|
+
authenticate!
|
7
|
+
halt([401, "Unauthorized User"]) unless teams.any? { |team_id| github_team_access?(team_id) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/script/bootstrap
ADDED
data/script/cibuild
ADDED
data/script/console
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
bundle exec pry -r "./lib/jekyll-auth"
|
data/script/release
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Tag and push a release.
|
3
|
+
|
4
|
+
set -e
|
5
|
+
|
6
|
+
# Make sure we're in the project root.
|
7
|
+
|
8
|
+
cd $(dirname "$0")/..
|
9
|
+
|
10
|
+
# Build a new gem archive.
|
11
|
+
|
12
|
+
rm -rf jekyll-auth-*.gem
|
13
|
+
gem build -q jekyll-auth.gemspec
|
14
|
+
|
15
|
+
# Make sure we're on the master branch.
|
16
|
+
|
17
|
+
(git branch | grep -q '* master') || {
|
18
|
+
echo "Only release from the master branch."
|
19
|
+
exit 1
|
20
|
+
}
|
21
|
+
|
22
|
+
# Figure out what version we're releasing.
|
23
|
+
|
24
|
+
tag=v`ls jekyll-auth-*.gem | sed 's/^jekyll-auth-\(.*\)\.gem$/\1/'`
|
25
|
+
|
26
|
+
# Make sure we haven't released this version before.
|
27
|
+
|
28
|
+
git fetch -t origin
|
29
|
+
|
30
|
+
(git tag -l | grep -q "$tag") && {
|
31
|
+
echo "Whoops, there's already a '${tag}' tag."
|
32
|
+
exit 1
|
33
|
+
}
|
34
|
+
|
35
|
+
# Tag it and bag it.
|
36
|
+
|
37
|
+
gem push jekyll-auth-*.gem && git tag "$tag" &&
|
38
|
+
git push origin master && git push origin "$tag"
|
data/script/server
ADDED
data/script/setup
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "logged in user" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
JekyllAuth.site
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
setup_tmp_dir
|
12
|
+
@user = make_user('login' => 'benbaltertest')
|
13
|
+
login_as @user
|
14
|
+
|
15
|
+
ENV['GITHUB_ORG_ID'] = "balter-test-org"
|
16
|
+
|
17
|
+
stub_request(:get, "https://api.github.com/orgs/#{ENV["GITHUB_ORG_ID"]}/members/benbaltertest").
|
18
|
+
to_return(:status => 200)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "shows the securocat when github returns an oauth error" do
|
22
|
+
get "/auth/github/callback?error=redirect_uri_mismatch"
|
23
|
+
expect(last_response.body).to match(%r{securocat\.png})
|
24
|
+
end
|
25
|
+
|
26
|
+
it "logs the user out" do
|
27
|
+
get "/logout"
|
28
|
+
expect(last_response.status).to eql(302)
|
29
|
+
expect(last_response.headers['Location']).to eql("http://example.org/")
|
30
|
+
|
31
|
+
get "/"
|
32
|
+
expect(last_response.status).to eql(302)
|
33
|
+
expect(last_response.headers['Location']).to match(%r{^https://github\.com/login/oauth/authorize})
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "logged out user" do
|
39
|
+
|
40
|
+
include Rack::Test::Methods
|
41
|
+
|
42
|
+
def app
|
43
|
+
JekyllAuth.site
|
44
|
+
end
|
45
|
+
|
46
|
+
before do
|
47
|
+
ENV['GITHUB_ORG_ID'] = "balter-test-org"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "doesn't let you view indexes" do
|
51
|
+
get "/"
|
52
|
+
expect(last_response.status).to eql(302)
|
53
|
+
expect(last_response.headers['Location']).to match(%r{^https://github\.com/login/oauth/authorize})
|
54
|
+
|
55
|
+
get "/some_dir"
|
56
|
+
expect(last_response.status).to eql(302)
|
57
|
+
expect(last_response.headers['Location']).to match(%r{^https://github\.com/login/oauth/authorize})
|
58
|
+
end
|
59
|
+
|
60
|
+
it "doesn't let you view files" do
|
61
|
+
get "/index.html"
|
62
|
+
expect(last_response.status).to eql(302)
|
63
|
+
expect(last_response.headers['Location']).to match(%r{^https://github\.com/login/oauth/authorize})
|
64
|
+
|
65
|
+
get "/some_dir/index.html"
|
66
|
+
expect(last_response.status).to eql(302)
|
67
|
+
expect(last_response.headers['Location']).to match(%r{^https://github\.com/login/oauth/authorize})
|
68
|
+
end
|
69
|
+
|
70
|
+
it "refuses to serve the site without an authentication strategy" do
|
71
|
+
ENV["GITHUB_ORG_ID"] = nil
|
72
|
+
ENV["GITHUB_TEAM_ID"] = nil
|
73
|
+
ENV["GITHUB_TEAMS_ID"] = nil
|
74
|
+
expect{get "/"}.to raise_error(JekyllAuth::ConfigError)
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "bin" do
|
4
|
+
before(:each) do
|
5
|
+
setup_tmp_dir
|
6
|
+
end
|
7
|
+
|
8
|
+
it "spits out the help do" do
|
9
|
+
env = { "GITHUB_TOKEN" => nil}
|
10
|
+
output = execute_bin(env, "--help")
|
11
|
+
expect(output).to match(%r{A simple way to use Github OAuth to serve a protected jekyll site to your GitHub organization})
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "team id" do
|
15
|
+
|
16
|
+
it "errors if no token is given" do
|
17
|
+
env = { "GITHUB_TOKEN" => nil}
|
18
|
+
expect{execute_bin(env, "team_id", "--org", "balter-test-org", "--team", "1")}.to raise_error(RuntimeError).
|
19
|
+
with_message(/prefix the jekyll-auth command with GITHUB_TOKEN/)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "errors if no team_id or org_id is given" do
|
23
|
+
env = { "GITHUB_TOKEN" => "1234"}
|
24
|
+
expect{execute_bin(env, "team_id")}.to raise_error(RuntimeError).
|
25
|
+
with_message(/An org name and team ID are required/)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "initializes a new site" do
|
30
|
+
`git init`
|
31
|
+
`git add .`
|
32
|
+
`git commit -m 'initial commit'`
|
33
|
+
execute_bin({"RACK_ENV" => "TEST"}, "new")
|
34
|
+
expect(File).to exist("#{tmp_dir}/config.ru")
|
35
|
+
expect(File).to exist("#{tmp_dir}/Rakefile")
|
36
|
+
expect(File).to exist("#{tmp_dir}/.gitignore")
|
37
|
+
expect(File).to exist("#{tmp_dir}/.env")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "builds the site" do
|
41
|
+
execute_bin({}, "build")
|
42
|
+
expect(File).to exist("#{tmp_dir}/_site/index.html")
|
43
|
+
end
|
44
|
+
end
|