|
# Jekyll plugin: /_plugins/jekyll_auth.rb |
|
|
|
# DEBUG |
|
# require 'debugger' |
|
|
|
module Jekyll |
|
|
|
# Jekyll Auth Plugin - Plugin to manage http basic auth for jekyll generated pages and directories. |
|
# |
|
# Configuration: |
|
# |
|
# - ``auth_local_user_file``: |
|
# full qualified path to the locally generated user auth file; |
|
# default: ``/tmp/.jekyll_user_file``. |
|
# - ``auth_remote_user_file``: |
|
# full qualified path to the user auth file, when it is deployed. is written |
|
# into the .htaccess files; no default value. |
|
# - ``auth_local_group_file``: |
|
# full qualified path to the locally generated group auth file; |
|
# default: ``/tmp/.jekyll_group_file``. |
|
# - ``auth_remote_group_file``: |
|
# full qualified path to the group auth file, when it is deployed. is written |
|
# into the .htaccess files; no default value. |
|
# |
|
# |
|
class AuthGenerator < Generator |
|
|
|
priority :high |
|
|
|
def generate site |
|
puts; puts "#### Jekyll::Auth" |
|
|
|
# Read in data |
|
auth = Auth.new site |
|
|
|
# Validate |
|
auth.validate! |
|
|
|
# Write user file |
|
size = auth.users.size |
|
puts "#{size} User#{size>1||size==0 ? 's' : ''} found#{size>0 ? ':' : ''}" # Logging Users |
|
auth.generate_user_file |
|
|
|
# Write group file |
|
size = auth.groups.size |
|
puts "#{size} Group#{size>1||size==0 ? 's' : ''} found#{size>0 ? ':' : ''}" # Logging Groups |
|
auth.groups.values.each do |group| |
|
puts " - #{group.groupname}: #{group.users.join(' ')}" |
|
end |
|
auth.generate_group_file |
|
|
|
# Logging Resources |
|
size = auth.directories.size |
|
puts "#{size} Director#{size>1||size==0 ? 'ies' : 'y'} with restricted access found#{size>0 ? ':' : ''}" |
|
auth.directories.values.each do |dir| |
|
puts " - #{dir.dir}" |
|
end |
|
size = auth.files.size |
|
puts "#{size} File#{size>1||size==0 ? 's' : ''} with restricted access (" + |
|
"not supported yet".red + ") found#{size>0 ? ':' : ''}" |
|
auth.files.values.each do |file| |
|
puts " - #{file.path}" |
|
end |
|
|
|
# Write directives into .htaccess files |
|
auth.write_directives(site) |
|
end |
|
|
|
end |
|
|
|
class Auth |
|
attr_reader :users, :groups, :directories, :files, |
|
:remote_user_file, :remote_group_file |
|
|
|
def initialize site |
|
@users = {}; @groups = {} |
|
@directories = {}; @files = {} |
|
|
|
read_config site |
|
read_posts site |
|
|
|
# TODO: validate!! |
|
end |
|
|
|
def read_config site |
|
@local_user_file = site.config['auth_local_user_file'] || '/tmp/.jekyll_user_file' |
|
@remote_user_file = site.config['auth_remote_user_file'] |
|
@local_group_file = site.config['auth_local_group_file'] || '/tmp/.jekyll_group_file' |
|
@remote_group_file = site.config['auth_remote_group_file'] |
|
|
|
add_users site.config['auth_users'] if(site.config['auth_users']) |
|
add_groups site.config['auth_groups'] if(site.config['auth_groups']) |
|
add_dirs site.config['auth_dirs'] if (site.config['auth_dirs']) |
|
end |
|
|
|
def read_posts site |
|
# collect all posts and pages with auth info |
|
payload = site.site_payload |
|
auth_pp = payload['site']['posts'].concat(payload['site']['pages']).select do |post| |
|
post.data.has_key?('auth_users') || post.data.has_key?('auth_groups') |
|
end |
|
# read auth info from every page or post |
|
auth_pp.each do |p| |
|
p_users = p.data['auth_users'] ? add_users(p.data['auth_users']) : [] |
|
p_groups = p.data['auth_groups'] ? add_groups(p.data['auth_groups']) : [] |
|
if (p.data['layout'] == 'set') |
|
p.data['auth_dir'] = add_dir(p.url, p.data['auth_users'], p.data['auth_groups'], p.data['auth_valid_user']) |
|
end |
|
# TODO AuthFiles! |
|
end |
|
end |
|
|
|
def validate! |
|
unless (invalid_users = @users.values.select{|user| !user.valid?}).empty? |
|
raise Exception.new "Invalid Auth-Users found in your config/posts: #{invalid_users.map(&:username).join(', ')}" |
|
end |
|
unless (invalid_groups = @groups.values.select{|group| !group.valid?}).empty? |
|
raise Exception.new "Invalid Auth-Groups found in your config/posts: #{invalid_groups.map(&:groupname).join(', ')}" |
|
end |
|
end |
|
|
|
def generate_user_file |
|
# create user_file directory if not existant yet |
|
user_dir = File.dirname(@local_user_file) |
|
FileUtils.mkdir_p(user_dir) unless File.exists?(user_dir) |
|
# generate user entries in user file |
|
@users.values.each_with_index do |user, i| |
|
if i == 0 |
|
user.generate_user_file @local_user_file |
|
else |
|
user.update_user_file @local_user_file |
|
end |
|
end |
|
end |
|
|
|
def generate_group_file |
|
# create group_file directory if not existant yet |
|
group_dir = File.dirname(@local_group_file) |
|
FileUtils.mkdir_p(group_dir) unless File.exists?(group_dir) |
|
# generate group entries in group file |
|
File.open(@local_group_file, 'w') do |groupfile| |
|
@groups.values.each do |auth_group| |
|
groupfile.puts "#{auth_group.groupname}: #{auth_group.users.join(' ')}" |
|
end |
|
end |
|
end |
|
|
|
def write_directives site |
|
create_htaccess_files site |
|
end |
|
|
|
def create_htaccess_files site |
|
directories.values.each do |auth_dir| |
|
site.pages << HtaccessFile.new(site, site.source, auth_dir, self) |
|
end |
|
end |
|
|
|
# Hash with username => password mapping or String with space seperated |
|
# usernames |
|
# Returns Array of AuthUser objects |
|
def add_users hash_or_string |
|
new_users = [] |
|
if hash_or_string.is_a?(String) |
|
hash_or_string.split(' ').each do |uname| |
|
new_users << add_user(uname) |
|
end |
|
elsif hash_or_string.is_a?(Hash) |
|
hash_or_string.each do |uname, pword| |
|
new_users << add_user(uname, pword) |
|
end |
|
end |
|
new_users |
|
end |
|
|
|
def add_user username, password=nil |
|
if !@users[username] |
|
@users[username] = AuthUser.new(username, password) |
|
elsif password |
|
if !@users[username].password |
|
@users[username].password = password |
|
elsif password != @users[username].password |
|
raise Exception.new "User '#{username}' specified with different passwords!" |
|
end |
|
end |
|
@users[username] |
|
end |
|
|
|
def add_groups hash_or_string |
|
new_groups = [] |
|
if hash_or_string.is_a?(String) |
|
hash_or_string.split(' ').each do |gname| |
|
new_groups << add_group(gname) |
|
end |
|
elsif hash_or_string.is_a?(Hash) |
|
hash_or_string.each do |gname, users| |
|
new_groups << add_group(gname, users) |
|
end |
|
end |
|
new_groups |
|
end |
|
|
|
def add_group name, users='' |
|
if !@groups[name] |
|
@groups[name] = AuthGroup.new(name, users.split(' ')) |
|
elsif !users.empty? |
|
@groups[name].users += users.split(' ') |
|
end |
|
@groups[name] |
|
end |
|
|
|
# dirs is a Hash: |
|
# with dirnames as keys and |
|
# a Hash with auth_users, auth_groups, auth_valid_user as its values |
|
def add_dirs dirs |
|
new_dirs = [] |
|
dirs.each do |dir_name, dir| |
|
new_dirs << add_dir(dir_name, dir['auth_users'], dir['auth_groups'], dir['auth_valid_user']) |
|
end |
|
new_dirs |
|
end |
|
|
|
def add_dir dir_name, users, groups, valid_user |
|
# add users |
|
new_users = users ? add_users(users) : [] |
|
# add groups |
|
new_groups = groups ? add_groups(groups) : [] |
|
|
|
# add dirs |
|
if !@directories[dir_name] |
|
@directories[dir_name] = AuthDirectory.new(dir_name, new_users, new_groups, valid_user) |
|
else |
|
@directories[dir_name].users += new_users |
|
@directories[dir_name].groups += new_groups |
|
end |
|
@directories[dir_name] |
|
end |
|
|
|
end # end Auth |
|
|
|
|
|
class HtaccessFile < Page |
|
# override Page#dir: which returns / or the path as described with #permalink |
|
attr_accessor :dir |
|
def initialize(site, base, auth_dir, auth) |
|
@site = site |
|
@base = base |
|
@dir = auth_dir.dir |
|
@name = '.htaccess' |
|
|
|
self.process(@name) |
|
self.read_yaml(File.join(base, '_layouts'), '.htaccess') |
|
# pass data to the 'page' |
|
self.data['auth_dir'] = auth_dir |
|
self.data['auth_remote_user_file'] = auth.remote_user_file |
|
self.data['auth_remote_group_file'] = auth.remote_group_file |
|
end |
|
end |
|
|
|
|
|
class AuthResource |
|
attr_reader :dir |
|
attr_accessor :users, :groups |
|
# users, groups are Arrays of AuthUser and AuthGroup objects |
|
# valid_user is a boolean |
|
def initialize dir, users, groups, valid_user |
|
@users = users; |
|
@valid_user = valid_user |
|
@groups = groups; |
|
@dir = dir |
|
end |
|
|
|
def to_liquid |
|
{ |
|
'users' => @users.map(&:username).uniq, |
|
'groups' => @groups.map(&:groupname).uniq, |
|
'valid_user' => @valid_user, |
|
'dir' => @dir |
|
} |
|
end |
|
end |
|
|
|
|
|
class AuthDirectory < AuthResource |
|
end |
|
|
|
# TODO: wird noch nicht geschrieben |
|
class AuthFile < AuthResource |
|
attr_reader :file, :path |
|
def initialize path, users, groups, valid_user |
|
super File.dirname(path), users, groups, valid_user |
|
@path = path |
|
@file = File.basename(path) |
|
end |
|
|
|
def to_liquid |
|
super.merge({ |
|
'file' => @file |
|
}) |
|
end |
|
end |
|
|
|
|
|
class AuthUser |
|
attr_reader :username |
|
attr_accessor :password |
|
|
|
def initialize username, password |
|
@username = username |
|
@password = password |
|
end |
|
|
|
# username and password not empty, no spaces |
|
def valid? |
|
!username.nil? && !password.nil? && !username.empty? && !password.empty? && |
|
username.index(/\s/).nil? && password.index(/\s/).nil? |
|
end |
|
|
|
def generate_user_file user_file_path |
|
`htpasswd -cb #{user_file_path} #{@username} #{@password}` |
|
end |
|
|
|
def update_user_file user_file_path |
|
`htpasswd -b #{user_file_path} #{@username} #{@password}` |
|
end |
|
end |
|
|
|
class AuthGroup |
|
attr_reader :groupname |
|
attr_accessor :users |
|
|
|
# users: Array of usernames |
|
def initialize groupname, users=[] |
|
@groupname = groupname |
|
@users = users |
|
end |
|
|
|
# groupname not empty, no spaces, users.any? |
|
def valid? |
|
!groupname.nil? && !groupname.empty? && groupname.index(/\s/).nil? && users.any? |
|
end |
|
end |
|
|
|
end |
Well I've been trying this out, and apparently it seems to build something, too - but I still can access the stuff I'm supposed not to be accessing without a password prompt.
Jekyll::Auth
2 Users found:
Adding password for user hans
Adding password for user buenaventura
1 Group found:
5 Directories with restricted access found:
0 Files with restricted access (not supported yet) found
Generating Github feed from rss using feedzirra with github_feed.rb
done.
Server address: http://127.0.0.1:4000/
Server running... press ctrl-c to stop.
That means everything at 127.0.0.1:4000/projects for example should require a password, right? Am I missing something here? Does this only work when using Apache2 and my WebRick based testing environment is just not working for this?