Skip to content

Instantly share code, notes, and snippets.

@cessor
Last active October 15, 2018 09:10
Show Gist options
  • Select an option

  • Save cessor/fd1b0de99110263a35debe3a93621767 to your computer and use it in GitHub Desktop.

Select an option

Save cessor/fd1b0de99110263a35debe3a93621767 to your computer and use it in GitHub Desktop.

HROOT Admin Setup

Hroot's default admin account poses a security threat. This code provides a rake task to

  • Create Admin Accounts within HROOT
  • Delete Admin Accounts within HROOT
  • Delete the Default Admin Account within HROOT

Author: Johannes C. Hofmeister, https://cessor.de, July 2018

License: I really don't care. Be excellent to each other. Not liable if you mess things up.

Installation

  1. Copy prompt.rb to <hroot>/lib
  2. Copy setup.rake to <hroot>/lib/tasks

Usage

  1. On the command line, get a login shell
  2. Enter the HROOT source Directory
  3. Invoke Rake:

$ RAILS_ENV=test rake --tasks

The tasks should show up under the hroot tag:

...
rake hroot:create_admin                                # Create admin
rake hroot:remove_admin[email_address]                 # Remove an admin account by email
rake hroot:remove_default_admin                        # Remove default admin account
rake hroot:remove_duplicate_participants               # Remove duplicates from participations table (fix for bug)
...

Please note: In the above command, I state the Rails Environment explicitly as RAILS_ENV=test, but be aware that there are three other databases as well.

It is important that you remove the default admin from the production environment!

1. Removing the Default Admin

% RAILS_ENV=production rake hroot:remove_default_admin
OK (Not found)

The tool will tell you whether it removed the admin account, or whether the account was gone anyway.

2. Creating a new Admin

To create an admin account, use the following command:

 % RAILS_ENV=test rake hroot:create_admin                             

This will invoke a dialog. If you mistype or leave a value empty, the system will ask you until you provided a valid value (e.g. the email address must be valid)

% RAILS_ENV=test rake hroot:create_admin                             
Creating HROOT Admin Account
----------------------------

Firstname: Johannes 
Lastname: Hofmeister
Email: awdawd
Email: johannes.hofmeister@example.com

Please specify and confirm your password!
Provide at least 8 characters and at least 1 digit.

Key strokes will be hidden.
Password: 
Confirm password: 

Successfully created administrator account.
=begin
Place in <hroot>/lib/
This module provides primitives for console user inputs.
It asks for values until a condition is met. This can be used, for example, to
ask for an email address until the user provides a valid address, e.g.
```
email = ValidEmailAddress.new Text.new 'Email'
address = email.to_s
```
A convenience function is provided:
```
password = ask_for "Password", Password, ComplicatedString
password = password.to_s
```
Objects are lazy, which means they only invoke their functionality
when asking you ask them for their value with to_s.
To create a new Prompt, inherit from Prompt, and override the .input-method.
To create a requirement, inherit from Requirement, and implement the
.met?-method.
Author: Johannes C. Hofmeister, https://cessor.de, July 2018
License: I really don't care. Be excellent to each other. Not liable if you mess things up.
Disclaimer at end of file.
=end
class Prompt
# Base class for prompts.
# Displays a message for the user and them for input from the console.
protected
def initialize(message)
@message = message
end
def input
raise NotImplementedError
end
def prompt_message
@message.to_s + ": "
end
end
class Text < Prompt
# Asks the user for input from the console.
# This is capable of blocking Rake tasks
# A simple gets doesn't work.
def input
print prompt_message
value = STDIN.gets
value.chomp
end
end
class Password < Prompt
# Asks the user for input from the console.
# Typed characters are hidden
def input
value = STDIN.getpass(prompt_message)
value.chomp
end
end
class Pair
# A pair of two values which defaults to the first
# Used by Confirmation to provide multiple return values
def initialize(value1, value2)
@value1 = value1
@value2 = value2
end
def match?
@value1.eql? @value2
end
def to_s
@value1
end
end
class Confirmation < Prompt
# Prompts the user for values and returns them
# them as a pair
def initialize(message, prompt1, prompt2)
@message = message
@prompt1 = prompt1
@prompt2 = prompt2
end
def input
print @message
Pair.new @prompt1.to_s, @prompt2.to_s
end
end
class Requirement
# Base class for requirements
# A requirement will prompt for input until the requirement is met.
def to_s
response = ''
while not met?(response) do
response = @prompt.input
end
response.to_s
end
protected
def initialize(prompt)
@prompt = prompt
end
def met?(value)
raise NotImplementedError
end
end
class NotEmpty < Requirement
# Providing documentation for this class seems unnecessary.
# F***ing guess what it does.
def met?(value)
not value.to_s.empty?
end
end
class ComplicatedString < Requirement
# Make sure a value looks like some kind of password-like string
# This does not really enforce strong passwords, but what can you do...
# Ideally, this should enforce even stronger values.
def met?(value)
value.length >= 8 and value =~ /\d/
end
end
class ValidEmailAddress < Requirement
# I did not actually test this Regex. I don't know. Give it something
# with an @ in the middle, yes?
EMAIL_PATTERN = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
def met?(value)
value =~ EMAIL_PATTERN
end
end
class ValuesMatch < Requirement
# Used to make sure that the password and its confirmation match
def met?(pair)
not pair.is_a? String and pair.match?
end
end
def ask_for(message, prompt, requirement)
# Factory method for convenience & readability
requirement.new prompt.new message
end
=begin
Disclaimer
==========
I am a Python programmer by trade, and I am unfamiliar with Ruby coding
conventions. I briefly skimmed https://github.com/rubocop-hq/ruby-style-guide.
It might be that a library to get user input from the console already exists
in Ruby, I didn't check as I wanted to learn some ruby.
I am trying to write idiomatic Ruby code, but I default to Python idioms when
in doubt.
Prompt and Requirement are abstract base classes. I wanted to compose prompts
and requirements, rather than inherit from a base class but I felt that Ruby
semantic were too complicated. For example, Lambdas and Procs require to be
invoked with .call, so I went with a proper object with a more meaningful
method name instead (Requirement.met?).
The reasoning for this OOP style is described in the book "Elegant Objects" by
Yegor Bugayenko. The gist of it is to have many tiny, active objects, and to
use objects as the central unit of expression.
=end
require 'prompt'
=begin
Post Install Tasks for Hroot.
Place in <hroot>/tasks/
Author: Johannes C. Hofmeister, https://cessor.de, July 2018
License: I really don't care. Be excellent to each other. Not liable if you mess things up.
=end
namespace :hroot do
# Todo
# ----
# [ ] Ask for stronger passwords. I guess an administrator can
# be expected to provide a password with sufficient strength
#
desc 'Remove an admin account by email'
task :remove_admin, [:email_address] => [:environment] do |t, args|
# Remove an admin user from the commandline.
# Call it with
# $ RAILS_ENV=<env> bundle exec rake hroot:remove_admin[email@example.com]
#
# Note: You might have to escape the square brackets, e.g., for zsh
# https://stackoverflow.com/questions/825748/how-to-pass-command-line-arguments-to-a-rake-task#comment77647948_29502094
# Source: https://stackoverflow.com/questions/825748/how-to-pass-command-line-arguments-to-a-rake-task#825832
email_address = args[:email_address]
user = User.find_by(email: email_address, role: 'admin')
if user
user.destroy
puts "OK (Removed)"
else
puts "OK (Not found)"
end
end
desc 'Remove default admin account'
task :remove_default_admin => [:environment] do
# HROOT comes with a default admin account
# The default admin is a security risk, because such accounts are easily
# forgotten after install, or even left for convenience,
# thus exposing an exploitable access method to anyone who
# is aware that this account exists (or even for brute force attacks, as
# the set password is not that strong).
default_email = "admin@domain.net"
Rake::Task['hroot:remove_admin'].invoke(default_email)
end
def confirmed_password
message = "\nPlease specify and confirm your password!\n"\
"Provide at least 8 characters and at least 1 digit.\n\n"\
"Key strokes will be hidden.\n"
ValuesMatch.new Confirmation.new(
message,
ask_for('Password', Password, ComplicatedString),
ask_for('Confirm password', Password, ComplicatedString)
)
end
desc 'Create admin'
task :create_admin => :environment do
puts 'Creating HROOT Admin Account'
puts '----------------------------'
puts
firstname = ask_for('Firstname', Text, NotEmpty).to_s
lastname = ask_for('Lastname', Text, NotEmpty).to_s
email = ask_for('Email', Text, ValidEmailAddress).to_s
password = confirmed_password.to_s
if User.find_by(email: email)
puts "A user with email #{email} already exists"
exit
end
admin = User.new(
:firstname => firstname,
:lastname => lastname,
:email => email,
:password => password,
:password_confirmation => password,
:role => "admin",
:matrikel => "admin"
)
admin.admin_update = true
# Prevent sending emails through devise
admin.skip_confirmation!
admin.calendar_key = SecureRandom.hex(16)
admin.save(:validate => false)
if User.find_by(email: email)
puts
puts 'Successfully created administrator account.'
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment