├── .gitignore
├── .rspec
├── .ruby-gemset
├── .ruby-version
├── .travis.yml
├── .yardopts
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Procfile
├── README.md
├── Rakefile
├── app
├── assets
│ ├── images
│ │ └── .keep
│ ├── javascripts
│ │ ├── application.js.erb
│ │ └── time.js.coffee
│ └── stylesheets
│ │ ├── application.css.scss.erb
│ │ ├── connect.css.scss
│ │ ├── devise
│ │ └── sessions.css.scss
│ │ └── users.css.scss
├── controllers
│ ├── application_controller.rb
│ ├── concerns
│ │ ├── .keep
│ │ ├── github_oauth_concern.rb
│ │ ├── github_settings_mixin.rb
│ │ ├── settings_mixin.rb
│ │ └── setup_mixin.rb
│ ├── connect_controller.rb
│ ├── dashboard_controller.rb
│ ├── github_users_controller.rb
│ ├── settings_controller.rb
│ ├── setup
│ │ ├── admin_user_controller.rb
│ │ ├── company_controller.rb
│ │ ├── email_controller.rb
│ │ ├── github_controller.rb
│ │ ├── ldap_controller.rb
│ │ └── rules_controller.rb
│ └── users_controller.rb
├── helpers
│ ├── application_helper.rb
│ └── github_users_helper.rb
├── jobs
│ └── connect_github_user_job.rb
├── mailers
│ ├── .keep
│ └── user_mailer.rb
├── models
│ ├── .keep
│ ├── concerns
│ │ ├── .keep
│ │ └── encryptable.rb
│ ├── connect_github_user_status.rb
│ ├── github_email.rb
│ ├── github_organization_membership.rb
│ ├── github_team.rb
│ ├── github_user.rb
│ ├── setting.rb
│ └── user.rb
└── views
│ ├── connect
│ ├── _connect_step.html.erb
│ └── index.html.erb
│ ├── dashboard
│ └── index.html.erb
│ ├── devise
│ ├── sessions
│ │ └── new.html.erb
│ └── shared
│ │ └── _links.erb
│ ├── github_users
│ ├── index.html.erb
│ └── show.html.erb
│ ├── layouts
│ └── application.html.erb
│ ├── settings
│ ├── _active_directory.html.erb
│ ├── _company.html.erb
│ ├── _email.html.erb
│ ├── _github.html.erb
│ ├── _rules.html.erb
│ └── edit.html.erb
│ ├── setup
│ ├── admin_user
│ │ └── new.html.erb
│ ├── company
│ │ └── edit.html.erb
│ ├── email
│ │ └── edit.html.erb
│ ├── github
│ │ └── edit.html.erb
│ ├── ldap
│ │ └── edit.html.erb
│ └── rules
│ │ └── edit.html.erb
│ ├── user_mailer
│ ├── access_revoked.html.erb
│ └── access_revoked.text.erb
│ └── users
│ ├── _github_user.html.erb
│ ├── _github_users.html.erb
│ ├── _ldap_user.html.erb
│ ├── edit.html.erb
│ ├── index.html.erb
│ └── show.html.erb
├── bin
├── bundle
├── delayed_job
├── rails
├── rake
├── setup
└── spring
├── config.ru
├── config
├── application.rb
├── boot.rb
├── database.yml.example
├── database.yml.travis
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── action_mailer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── cookies_serializer.rb
│ ├── devise.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── session_store.rb
│ ├── state_machine_patch.rb
│ └── wrap_parameters.rb
├── locales
│ ├── devise.en.yml
│ └── en.yml
├── routes.rb
├── secrets.yml.example
└── secrets.yml.travis
├── cookbook
├── .kitchen.yml
├── Berksfile
├── Berksfile.lock
├── Gemfile
├── Gemfile.lock
├── README.md
├── attributes
│ ├── database.rb
│ ├── default.rb
│ ├── engines.rb
│ ├── nginx.rb
│ ├── ruby.rb
│ └── ssh.rb
├── libraries
│ └── github_connector_helpers.rb
├── metadata.rb
├── recipes
│ ├── cron.rb
│ ├── database.rb
│ ├── default.rb
│ ├── nginx.rb
│ ├── ruby.rb
│ ├── server.rb
│ ├── ssh.rb
│ ├── upstart.rb
│ └── user.rb
├── templates
│ └── default
│ │ ├── database.yml.erb
│ │ ├── nginx-github-connector.conf.erb
│ │ ├── secrets.yml.erb
│ │ ├── upstart-github-connector-web.conf.erb
│ │ └── upstart-github-connector-worker.conf.erb
└── test_data_bags
│ └── github_connector
│ ├── secrets.json
│ ├── ssh.json
│ └── ssl_cert.json
├── cortex.yaml
├── db
├── migrate
│ ├── 20140619160007_devise_create_users.rb
│ ├── 20140624041139_add_github_attrs_to_user.rb
│ ├── 20140626181353_create_settings.rb
│ ├── 20140708224056_create_emails.rb
│ ├── 20140709045852_add_last_sync_to_user.rb
│ ├── 20140709191104_add_state_attrs_to_user.rb
│ ├── 20140714210644_add_sync_errors_to_user.rb
│ ├── 20140722192112_add_github_teams.rb
│ ├── 20140724141457_refactor_github_tables.rb
│ ├── 20140726214806_move_state_to_github_user.rb
│ ├── 20140811194159_add_github_urls.rb
│ ├── 20140818012538_add_admin_flag_to_user.rb
│ ├── 20140915164525_convert_settings_value_to_text.rb
│ ├── 20140917184213_create_delayed_jobs.rb
│ ├── 20140917184236_add_connect_github_user_statuses.rb
│ ├── 20140920200517_add_remember_token_to_user.rb
│ ├── 20141018212156_add_github_user_disabled_teams.rb
│ ├── 20160215025445_add_github_organization_memberships.rb
│ └── 20210311145806_add_user_department.rb
├── schema.rb
└── seeds.rb
├── ldap
├── README.md
├── base.ldif
├── clear.ldif
├── local.schema
├── run-server
└── slapd-test.conf.erb
├── lib
├── assets
│ └── .keep
├── base_executor.rb
├── github_admin.rb
├── github_connector
│ ├── navbar.rb
│ └── settings.rb
├── github_synchronizer.rb
├── ldap_synchronizer.rb
├── rules.rb
├── rules
│ ├── active_ldap.rb
│ ├── base.rb
│ ├── email.rb
│ ├── github_mfa.rb
│ ├── github_oauth.rb
│ ├── last_github_sync.rb
│ └── last_ldap_sync.rb
├── settings
│ ├── base.rb
│ └── definition.rb
├── tasks
│ ├── .keep
│ ├── github.rake
│ └── sync.rake
└── transition_github_users.rb
├── log
└── .keep
├── public
├── 404.html
├── 422.html
├── 500.html
└── robots.txt
├── spec
├── controllers
│ ├── connect_controller_spec.rb
│ ├── dashboard_controller_spec.rb
│ ├── github_users_controller_spec.rb
│ ├── settings_controller_spec.rb
│ ├── setup
│ │ ├── admin_user_controller_spec.rb
│ │ ├── company_controller_spec.rb
│ │ ├── email_controller_spec.rb
│ │ ├── github_controller_spec.rb
│ │ ├── ldap_controller_spec.rb
│ │ └── rules_controller_spec.rb
│ └── users_controller_spec.rb
├── factories
│ ├── github_email.rb
│ ├── github_organization_membership.rb
│ ├── github_team.rb
│ ├── github_user.rb
│ └── user.rb
├── helpers
│ ├── application_helper_spec.rb
│ └── github_users_helper_spec.rb
├── jobs
│ └── connect_github_user_job_spec.rb
├── lib
│ ├── github_admin_spec.rb
│ ├── github_connector
│ │ └── settings_spec.rb
│ ├── github_synchronizer_spec.rb
│ ├── ldap_synchronizer_spec.rb
│ ├── rules
│ │ ├── active_ldap_spec.rb
│ │ ├── base_spec.rb
│ │ ├── email_spec.rb
│ │ ├── github_mfa_spec.rb
│ │ ├── github_oauth_spec.rb
│ │ ├── last_github_sync_spec.rb
│ │ └── last_ldap_sync_spec.rb
│ ├── rules_spec.rb
│ ├── settings
│ │ └── base_spec.rb
│ └── transition_github_users_spec.rb
├── mailers
│ └── user_mailer_spec.rb
├── models
│ ├── connect_github_user_status_spec.rb
│ ├── github_team_spec.rb
│ ├── github_user_spec.rb
│ ├── setting_spec.rb
│ └── user_spec.rb
├── rails_helper.rb
├── spec_helper.rb
├── support
│ └── controller_helpers.rb
└── views
│ ├── connect
│ └── index.html.erb_spec.rb
│ ├── layouts
│ └── application.html.erb_spec.rb
│ └── settings
│ └── edit.html.erb_spec.rb
└── vendor
└── assets
├── javascripts
└── .keep
└── stylesheets
└── .keep
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle
2 | /cookbook/.kitchen
3 | /config/database.yml
4 | /config/secrets.yml
5 | /coverage
6 | /doc
7 | /ldap/slapd-test.conf
8 | /ldap/openldap-data
9 | /log/*.log*
10 | /public/assets
11 | /public/favicon.ico
12 | /tmp
13 | /vendor/bundle
14 | /vendor/engines
15 | /.yardoc
16 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | github-connector
2 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.3.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: ruby
3 | cache: bundler
4 | rvm:
5 | - 2.3.0
6 | before_script:
7 | - cp config/database.yml.travis config/database.yml
8 | - cp config/secrets.yml.travis config/secrets.yml
9 | - bundle exec rake db:create db:migrate
10 |
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --markup=markdown
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | GitHub Connector CHANGELOG
2 | ==========================
3 |
4 | v0.1.5
5 | ------
6 | - Upgrade Octokit to latest version [#13]
7 |
8 | v0.1.4
9 | ------
10 | - Add simple JSON API [#12]
11 |
12 | v0.1.3
13 | ------
14 | - Update to Ruby 2.3.0
15 |
16 | v0.1.2
17 | ------
18 | - Update chef cookbook metadata
19 |
20 | v0.1.1
21 | ------
22 | - Update to Rails 4.2.1
23 | - Update cookbook to Ruby 2.2.1
24 | - Add a call to FileUtils.mkdir_p before running slapd [#2]
25 | - Check ConnectionStatus#error_message before rule failures [#4]
26 |
27 | v0.1.0
28 | ------
29 | - Initial open source release
30 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '~> 4.2.11.1'
4 |
5 | gem 'autoprefixer-rails'
6 | gem 'bootstrap-sass'
7 | gem 'coffee-rails', '~> 4.0.0'
8 | gem 'compass-rails'
9 | gem 'daemons'
10 | gem 'delayed_job_active_record'
11 | gem 'devise', '>= 3.4.0'
12 | gem 'devise_ldap_authenticatable', '> 0.8.1'
13 | gem 'friendly_id'
14 | gem 'font-awesome-rails'
15 | gem 'jquery-rails'
16 | gem 'oauth2'
17 | gem 'octokit', '> 3.3.1'
18 | gem 'pg'
19 | gem 'puma'
20 | gem 'sanitize'
21 | gem 'sass-rails'
22 | gem 'state_machine'
23 | gem 'turbolinks'
24 | gem 'uglifier', '>= 1.3.0'
25 |
26 | # Add local customizations via rails engines
27 | require 'pathname'
28 | engines_path = Pathname.new(__FILE__).parent.join('vendor', 'engines')
29 | if engines_path.exist?
30 | engines_path.each_child(false) do |engine_name|
31 | gem engine_name.to_s, path: File.join('vendor', 'engines', engine_name)
32 | end
33 | end
34 |
35 | group :development do
36 | gem 'foreman'
37 | gem 'spring'
38 | gem 'therubyracer'
39 | gem 'yard'
40 | end
41 |
42 | group :development, :test do
43 | gem 'database_cleaner'
44 | gem 'rspec-rails'
45 | end
46 |
47 | group :test do
48 | gem 'simplecov', :require => false
49 | gem 'factory_girl_rails'
50 | end
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Rapid7, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bundle exec rails s -p $PORT
2 | ldap: ldap/run-server
3 | worker: rake jobs:work
4 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js.erb:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require bootstrap-sprockets
16 | //= require turbolinks
17 | //= require_tree .
18 | <%
19 | # Require JS assets from custom engines
20 | engines_path = Rails.root.join('vendor', 'engines')
21 | if engines_path.exist?
22 | Rails.root.join('vendor', 'engines').each_child(false) do |engine_name|
23 | require_asset(engine_name.to_s)
24 | end
25 | end
26 | %>
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/time.js.coffee:
--------------------------------------------------------------------------------
1 | ready = ->
2 | $("span[data-time]").each (i, element) ->
3 | data = $(element).data()
4 | if data.time
5 | date = new Date(data.time)
6 | timezone = /\((.*)\)/.exec(date.toString())
7 | if timezone
8 | formatted_date = date.toLocaleString() + " " + timezone[1]
9 | else
10 | formatted_date = date.toString()
11 | $(element).html(formatted_date)
12 |
13 |
14 | $(document).ready(ready)
15 | $(document).on('page:load', ready)
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css.scss.erb:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *=require_self
14 | *=require_tree
15 | <%
16 | # Require CSS assets from custom engines
17 | engines_path = Rails.root.join('vendor', 'engines')
18 | if engines_path.exist?
19 | Rails.root.join('vendor', 'engines').each_child(false) do |engine_name|
20 | require_asset(engine_name.to_s)
21 | end
22 | end
23 | %>
24 | */
25 | $container-large-desktop: 970px;
26 | @import "compass/css3";
27 | @import "bootstrap-sprockets";
28 | @import "bootstrap";
29 | @import "font-awesome";
30 |
31 | .gh-main-nav {
32 | margin-bottom: 0;
33 | }
34 |
35 | .gh-main-content {
36 | margin-top: 20px;
37 | margin-bottom: 20px;
38 | &::before {
39 | display: block;
40 | content: "";
41 | }
42 | }
43 |
44 | .max-col-xs { max-width: 360px; }
45 | .max-col-sm { max-width: $container-sm; }
46 | .max-col-md { max-width: $container-md; }
47 | .max-col-lg { max-width: $container-lg; }
48 |
49 | .jumbotron {
50 | $jumbotron-color: darken($brand-primary, 15%);
51 |
52 | padding: 20px 0;
53 | margin-bottom: 0;
54 | color: lighten($brand-primary, 40%);
55 | @include single-text-shadow(0, 1px, 0, false, rgba(black, 0.1));
56 | @include background-image(linear-gradient(to top, $jumbotron-color, darken($jumbotron-color, 5%)));
57 |
58 | h1 {
59 | color: white;
60 | font-size: 50px;
61 | margin-top: 0;
62 | &:last-child { margin-bottom: 0; }
63 | }
64 |
65 | .btn-default {
66 | color: white;
67 | background-color: transparent;
68 | &:hover {
69 | color: $jumbotron-color;
70 | background-color: white;
71 | }
72 | }
73 | }
74 |
75 | @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
76 | .nav-sm-hide {
77 | display: none;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/connect.css.scss:
--------------------------------------------------------------------------------
1 | @import "bootstrap/variables";
2 |
3 | .connect-steps {
4 | .list-group-item {
5 | padding-left: 32px + $grid-gutter-width;
6 |
7 | &.active {
8 | color: inherit;
9 | background-color: $list-group-bg;
10 | border-color: $list-group-border;
11 | }
12 |
13 | .step-icon {
14 | display: none;
15 | float: left;
16 | font-size: 30px;
17 | margin-left: -(32px + ($grid-gutter-width / 2));
18 | &.step-icon-complete { color: green; }
19 | &.step-icon-error { color: red; }
20 | }
21 | span.step-icon {
22 | width: 32px;
23 | }
24 |
25 | &.active .step-icon-active { display: inline-block; }
26 | &.complete .step-icon-complete { display: inline-block; }
27 | &.complete .step-icon-active { display: none; }
28 | &.error .step-icon-error { display: inline-block; }
29 | }
30 |
31 | &.inprogress {
32 | .list-group-item.active {
33 | .step-icon-active { display: none; }
34 | .step-icon-loading { display: inline-block; }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/devise/sessions.css.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the devise/sessions controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 | body.sessions {
5 | background-color: #eee;
6 | }
7 |
8 | .form-signin {
9 | max-width: 330px;
10 | padding: 15px;
11 | margin: 0 auto;
12 | &.left {
13 | margin: 0;
14 | padding-left: 0;
15 | }
16 | }
17 | .form-signin .form-signin-heading,
18 | .form-signin .checkbox {
19 | margin-bottom: 10px;
20 | }
21 | .form-signin .checkbox {
22 | font-weight: normal;
23 | }
24 | .form-signin .form-control {
25 | position: relative;
26 | height: auto;
27 | -webkit-box-sizing: border-box;
28 | -moz-box-sizing: border-box;
29 | box-sizing: border-box;
30 | padding: 10px;
31 | font-size: 16px;
32 | }
33 | .form-signin .form-control:focus {
34 | z-index: 2;
35 | }
36 | .form-signin input[type="email"] {
37 | margin-bottom: -1px;
38 | border-bottom-right-radius: 0;
39 | border-bottom-left-radius: 0;
40 | }
41 | .form-signin input[type="password"] {
42 | margin-bottom: 10px;
43 | border-top-left-radius: 0;
44 | border-top-right-radius: 0;
45 | }
46 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/users.css.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the users controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 | .ldap-account, .github-account {
5 | h1, h2, h3, h4, h5, h6 {
6 | a { color: inherit; }
7 | a:hover { text-decoration: none; }
8 | }
9 |
10 | table .list-unstyled {
11 | margin-bottom: 0;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | rescue_from DeviseLdapAuthenticatable::LdapException do |exception|
3 | render :text => exception, :status => 500
4 | end
5 | # Prevent CSRF attacks by raising an exception.
6 | # For APIs, you may want to use :null_session instead.
7 | protect_from_forgery with: :exception
8 |
9 | before_action :check_configured
10 | before_action :authenticate_user!
11 | before_action :load_navbar
12 |
13 | private
14 | def check_configured
15 | unless Rails.application.settings.configured?
16 | redirect_to setup_url
17 | end
18 | end
19 |
20 | def require_admin
21 | return true if current_user.admin?
22 | render :status => :forbidden, :text => 'Forbidden'
23 | false
24 | end
25 |
26 | def load_navbar
27 | @navbar = GithubConnector::Navbar.new
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/concerns/github_oauth_concern.rb:
--------------------------------------------------------------------------------
1 | module GithubOauthConcern
2 | extend ActiveSupport::Concern
3 |
4 | protected
5 |
6 | def oauth_authenticity_token
7 | session[:_oauth_state] ||= SecureRandom.base64(32)
8 | end
9 |
10 | def oauth_client
11 | settings = Rails.application.settings
12 | @oauth_client ||= OAuth2::Client.new(settings.github_client_id, settings.github_client_secret,
13 | site: 'https://github.com/',
14 | authorize_url: '/login/oauth/authorize',
15 | token_url: '/login/oauth/access_token'
16 | )
17 | end
18 |
19 | def oauth_process_auth_code
20 | octokit = Octokit::Client.new(access_token: oauth_auth_code.token)
21 | ghuser = octokit.user
22 |
23 | github_user = GithubUser.find_or_initialize_by(id: ghuser.id)
24 | github_user.login = ghuser.login
25 | github_user.token = oauth_auth_code.token
26 | github_user.user = current_user
27 | github_user.sync!
28 | github_user
29 | end
30 |
31 | def oauth_scope
32 | settings = Rails.application.settings
33 | settings.github_user_oauth_scope
34 | end
35 |
36 | def oauth_validate_authenticity_token
37 | if oauth_state != oauth_authenticity_token
38 | raise ActionController::InvalidAuthenticityToken
39 | end
40 | end
41 |
42 | private
43 |
44 | def oauth_auth_code
45 | @oauth_auth_code ||= oauth_client.auth_code.get_token(oauth_code)
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/app/controllers/concerns/github_settings_mixin.rb:
--------------------------------------------------------------------------------
1 | module GithubSettingsMixin
2 | extend ActiveSupport::Concern
3 |
4 | def github_admin
5 | redirect_to oauth_client.auth_code.authorize_url(
6 | state: oauth_authenticity_token,
7 | scope: admin_oauth_scope,
8 | redirect_uri: url_for(action: 'github_auth_code')
9 | )
10 | end
11 |
12 | def github_auth_code
13 | oauth_validate_authenticity_token
14 | @github_user = oauth_process_auth_code
15 | Rails.application.settings.github_admin_token = oauth_auth_code.token
16 | flash.notice = "GitHub admin token updated successfully."
17 | redirect_to action: 'edit'
18 | end
19 |
20 | protected
21 |
22 | def admin_oauth_scope
23 | settings = Rails.application.settings
24 | settings.github_admin_oauth_scope
25 | end
26 |
27 | private
28 |
29 | def oauth_code
30 | params[:code]
31 | end
32 |
33 | def oauth_state
34 | params[:state]
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/app/controllers/concerns/settings_mixin.rb:
--------------------------------------------------------------------------------
1 | module SettingsMixin
2 | extend ActiveSupport::Concern
3 |
4 | PASSWORD_PLACEHOLDER = '|||PWPLACEHOLDER|||'
5 |
6 | included do
7 | before_filter :load_settings
8 | end
9 |
10 | def scrub_password(key)
11 | if @settings.dirty?(key)
12 | @settings.send(key)
13 | else
14 | PASSWORD_PLACEHOLDER
15 | end
16 | end
17 |
18 | private
19 | def keys
20 | Rails.application.settings.keys
21 | end
22 |
23 | def load_settings
24 | @settings = Rails.application.settings.load(keys).disconnect
25 | params = self.params[:settings] || {}
26 | keys.each do |key|
27 | if params.has_key?(key)
28 | next if params[key] == PASSWORD_PLACEHOLDER
29 | if @settings.definition(key).type == :array
30 | params[key] = params[key].split(/\r?\n/).map(&:strip).compact
31 | end
32 | @settings.send("#{key}=", params[key])
33 | end
34 | end
35 | end
36 |
37 | def test_ldap_connection
38 | ldap = Net::LDAP.new
39 | ldap.host = @settings.ldap_host
40 | ldap.port = @settings.ldap_port
41 | ldap.encryption :simple_tls if @settings.ldap_ssl
42 | ldap.auth @settings.ldap_admin_user, @settings.ldap_admin_password
43 | begin
44 | ldap.bind.tap do |result|
45 | @error = "Invalid admin user or password." unless result
46 | end
47 | rescue => e
48 | @error = e.message
49 | Rails.logger.warn "Cannot LDAP bind: #{e.class} - #{e.message}"
50 | false
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/app/controllers/concerns/setup_mixin.rb:
--------------------------------------------------------------------------------
1 | module SetupMixin
2 | extend ActiveSupport::Concern
3 |
4 | included do
5 | skip_before_filter :authenticate_user!
6 | before_filter :check_configured
7 | end
8 |
9 | private
10 | def apply_defaults
11 | default_settings.each do |key, val|
12 | @settings.send("#{key}=", val) unless @settings.send("#{key}")
13 | end
14 | end
15 |
16 | def check_configured
17 | if Rails.application.settings.configured?
18 | redirect_to settings_url
19 | end
20 | end
21 |
22 | # Attempts to figure out the domain name based on the
23 | # URL or company name
24 | #
25 | # @return [String]
26 | def default_domain
27 | if request.host == 'localhost' && !Rails.application.settings.company.blank?
28 | "#{Rails.application.settings.company.downcase.gsub(' ', '_')}.com"
29 | else
30 | request.host
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/controllers/connect_controller.rb:
--------------------------------------------------------------------------------
1 | require 'oauth2'
2 |
3 | class ConnectController < ApplicationController
4 | include GithubOauthConcern
5 | before_filter :load_connect_status, only: [:status]
6 |
7 | def index
8 | @connect_status = ConnectGithubUserStatus.new(
9 | step: :request
10 | )
11 | end
12 |
13 | def status
14 | render :index
15 | end
16 |
17 | def start
18 | redirect_to oauth_client.auth_code.authorize_url(
19 | state: oauth_authenticity_token,
20 | scope: oauth_scope,
21 | redirect_uri: oauth_redirect_uri
22 | )
23 | end
24 |
25 | def auth_code
26 | if params[:state] != oauth_authenticity_token
27 | raise ActionController::InvalidAuthenticityToken
28 | end
29 |
30 | connect_job_status = ConnectGithubUserStatus.create!(
31 | user: current_user,
32 | oauth_code: params[:code],
33 | status: :queued,
34 | step: :grant
35 | )
36 | ConnectGithubUserJob.perform_later(connect_job_status)
37 | redirect_to connect_status_path(connect_job_status)
38 | end
39 |
40 | protected
41 |
42 | def oauth_redirect_uri
43 | url_for action: 'auth_code'
44 | end
45 |
46 | private
47 |
48 | def load_connect_status
49 | @connect_status = ConnectGithubUserStatus.find(params[:id])
50 |
51 | if @connect_status.user_id != current_user.id
52 | render :status => :forbidden, :text => 'Forbidden'
53 | return false
54 | end
55 |
56 | true
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/app/controllers/dashboard_controller.rb:
--------------------------------------------------------------------------------
1 | class DashboardController < ApplicationController
2 | def index
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/github_users_controller.rb:
--------------------------------------------------------------------------------
1 | class GithubUsersController < ApplicationController
2 | before_filter :load_github_user, except: [:index]
3 | before_filter :require_admin
4 |
5 | def index
6 | # TODO: Pagination
7 | @github_users = GithubUser.includes(:user).order(:login)
8 | respond_to do |format|
9 | format.html
10 | format.json { render json: @github_users }
11 | end
12 | end
13 |
14 | def show
15 | respond_to do |format|
16 | format.html
17 | format.json { render json: @github_user }
18 | end
19 | end
20 |
21 | private
22 |
23 | def load_github_user
24 | @github_user = GithubUser.friendly.find(params[:id])
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/app/controllers/settings_controller.rb:
--------------------------------------------------------------------------------
1 | class SettingsController < ApplicationController
2 | include SettingsMixin
3 | include GithubOauthConcern
4 | include GithubSettingsMixin
5 | before_filter :require_admin
6 | before_filter :set_section_partials
7 |
8 | def edit
9 | end
10 |
11 | def update
12 | unless test_ldap_connection
13 | render :edit
14 | return
15 | end
16 | @settings.save
17 |
18 | if params[:connect_github]
19 | github_admin
20 | else
21 | flash.notice = "Settings saved successfully."
22 | redirect_to action: :edit
23 | end
24 | end
25 |
26 | def section_partials
27 | {
28 | 'Active Directory' => 'active_directory',
29 | 'GitHub' => 'github',
30 | 'Rules' => 'rules',
31 | 'Email' => 'email',
32 | }
33 | end
34 | private :section_partials
35 |
36 | def set_section_partials
37 | @section_partials = section_partials
38 | end
39 | private :set_section_partials
40 | end
41 |
--------------------------------------------------------------------------------
/app/controllers/setup/admin_user_controller.rb:
--------------------------------------------------------------------------------
1 | class Setup::AdminUserController < Devise::SessionsController
2 | include SetupMixin
3 | prepend_before_filter :sign_out_if_signed_in, only: [:new]
4 |
5 | def create
6 | super do |resource|
7 | resource.admin = true
8 | resource.save!
9 | flash.notice = ''
10 | end
11 | end
12 |
13 | protected
14 |
15 | def after_sign_in_path_for(resource)
16 | setup_github_path
17 | end
18 |
19 | def sign_out_if_signed_in
20 | if signed_in?
21 | Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/controllers/setup/company_controller.rb:
--------------------------------------------------------------------------------
1 | class Setup::CompanyController < ApplicationController
2 | include SetupMixin
3 | include SettingsMixin
4 |
5 | def edit
6 | apply_defaults unless @settings.company
7 | end
8 |
9 | def update
10 | @settings.save
11 |
12 | redirect_to setup_ldap_url
13 | end
14 |
15 | private
16 |
17 | def default_settings
18 | {
19 | }
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/controllers/setup/email_controller.rb:
--------------------------------------------------------------------------------
1 | class Setup::EmailController < ApplicationController
2 | include SetupMixin
3 | include SettingsMixin
4 |
5 | def edit
6 | apply_defaults unless @settings.smtp_address
7 | end
8 |
9 | def update
10 | @settings.save
11 |
12 | redirect_to setup_rules_url
13 | end
14 |
15 | private
16 |
17 | def default_settings
18 | {
19 | email_from: "github@#{default_domain}",
20 | email_base_url: root_url,
21 | smtp_address: "smtp.#{default_domain}",
22 | smtp_port: '25',
23 | smtp_enable_starttls_auto: true,
24 | }
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/controllers/setup/github_controller.rb:
--------------------------------------------------------------------------------
1 | class Setup::GithubController < ApplicationController
2 | include SetupMixin
3 | include SettingsMixin
4 | include GithubOauthConcern
5 | include GithubSettingsMixin
6 |
7 | def edit
8 | apply_defaults unless @settings.github_orgs
9 | end
10 |
11 | def update
12 | @settings.save
13 |
14 | if params[:connect_github]
15 | github_admin
16 | else
17 | redirect_to setup_email_url
18 | end
19 | end
20 |
21 | private
22 |
23 | def default_settings
24 | s = {
25 | github_check_mfa_team: 'github-connector-2fa-check',
26 | }
27 | unless Rails.application.settings.company.blank?
28 | s[:github_orgs] = [Rails.application.settings.company.downcase.gsub(' ', '-')]
29 | s[:github_default_teams] = ["#{Rails.application.settings.company.downcase.gsub(' ', '-')}-employees"]
30 | end
31 |
32 | s
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/controllers/setup/ldap_controller.rb:
--------------------------------------------------------------------------------
1 | class Setup::LdapController < ApplicationController
2 | include SetupMixin
3 | include SettingsMixin
4 |
5 | def edit
6 | apply_defaults unless @settings.ldap_host
7 | end
8 |
9 | def update
10 | unless test_ldap_connection
11 | render :edit
12 | return
13 | end
14 | @settings.save
15 |
16 | redirect_to setup_admin_url
17 | end
18 |
19 |
20 | private
21 |
22 | def default_settings
23 | {
24 | ldap_host: 'localhost',
25 | ldap_port: 3268,
26 | ldap_ssl: false,
27 | ldap_admin_user: "cn=admin,#{default_base}",
28 | ldap_admin_password: 'secret',
29 | ldap_base: default_base,
30 | ldap_attribute: 'sAMAccountName',
31 | }
32 | end
33 |
34 | def keys
35 | Rails.application.settings.ldap_keys
36 | end
37 |
38 | def default_base
39 | if request.host == 'localhost'
40 | 'dc=example,dc=com'
41 | else
42 | default_domain.split('.').map {|s| "dc=#{s}"}.join(',')
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/app/controllers/setup/rules_controller.rb:
--------------------------------------------------------------------------------
1 | class Setup::RulesController < ApplicationController
2 | include SetupMixin
3 | include SettingsMixin
4 |
5 | def edit
6 | apply_defaults unless @settings.rule_max_sync_age
7 | end
8 |
9 | def update
10 | @settings.save
11 |
12 | Rails.application.settings.configured = true
13 | flash.notice = "Setup Wizard completed successfully. You may verify settings below."
14 |
15 | redirect_to settings_url
16 | end
17 |
18 |
19 | private
20 |
21 | def default_settings
22 | {
23 | rule_max_sync_age: 86400,
24 | rule_email_regex: "@(#{default_domain.gsub('.', '\.')}|users\\.noreply\\.github\\.com)$",
25 | github_user_requirements: [
26 | 'Must enable two factor authentication ',
27 | 'Must only associate your company email address',
28 | ]
29 | }
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 | before_filter :load_user, except: [:index]
3 | before_filter :require_admin, except: [:show]
4 | before_filter :require_admin_or_user, only: [:show]
5 |
6 | def index
7 | # TODO: Pagination
8 | @users = User.includes(:github_users).order(:name)
9 | respond_to do |format|
10 | format.html
11 | format.json { render json: @users }
12 | end
13 | end
14 |
15 | def show
16 | respond_to do |format|
17 | format.html
18 | format.json { render json: @user }
19 | end
20 | end
21 |
22 | def edit
23 | end
24 |
25 | def update
26 | @user.update!(user_params)
27 | redirect_to @user
28 | end
29 |
30 | private
31 |
32 | def load_user
33 | @user = User.friendly.find(params[:id])
34 | end
35 |
36 | def require_admin_or_user
37 | return true if @user == current_user
38 | require_admin
39 | end
40 |
41 | def user_params
42 | params.require(:user).permit(:admin)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def current_user_path
3 | url_for controller: :users, action: :show, id: current_user ? current_user.username : nil
4 | end
5 |
6 | def jumbotron(&block)
7 | content_for(:jumbotron, &block)
8 | end
9 |
10 | def settings
11 | Rails.application.settings
12 | end
13 |
14 | def title(page_title)
15 | content_for(:title, page_title.to_s)
16 | end
17 |
18 | def nav_section(nav_section)
19 | content_for(:nav_section, nav_section)
20 | end
21 |
22 | def format_time(time)
23 | return nil unless time
24 | content_tag(:span, time.to_s, data: { time: time.utc.iso8601 })
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/helpers/github_users_helper.rb:
--------------------------------------------------------------------------------
1 | module GithubUsersHelper
2 | def github_user_state_label(github_user)
3 | state_class = case github_user.state
4 | when 'disabled' then 'label-danger'
5 | when 'unknown' then 'label-warning'
6 | when 'enabled' then 'label-success'
7 | when 'excluded' then 'label-info'
8 | when 'external' then 'label-info'
9 | end
10 |
11 | content_tag :span, github_user.human_state_name.capitalize, class: ['label', state_class].compact
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/jobs/connect_github_user_job.rb:
--------------------------------------------------------------------------------
1 | class ConnectGithubUserJob < ActiveJob::Base
2 | include GithubOauthConcern
3 | queue_as :default
4 |
5 | def perform(connect_status)
6 | @connect_status = connect_status
7 |
8 | @connect_status.update_attributes!(
9 | status: :running,
10 | step: :grant
11 | )
12 |
13 | # Process the user's token
14 | begin
15 | @github_user = oauth_process_auth_code
16 | rescue OAuth2::Error => e
17 | Rails.logger.warn "Cannot establish OAuth token: #{e.message}"
18 | @connect_status.update_attributes!(
19 | status: :error,
20 | error_message: e.description
21 | )
22 | return
23 | end
24 |
25 | @connect_status.update_attributes!(
26 | step: :add,
27 | github_user: @github_user
28 | )
29 |
30 | # Add to organizations
31 | unless @github_user.add_to_organizations
32 | @connect_status.update_attributes!(
33 | status: :error
34 | )
35 | return
36 | end
37 |
38 | # Enable user
39 | @github_user.enable if @github_user.can_enable?
40 |
41 | # Mark complete
42 | @connect_status.update_attributes!(
43 | status: :complete,
44 | step: :teams
45 | )
46 |
47 | rescue => e
48 | Rails.logger.error "Error running ConnectGithubUserJob: #{e}\n\t#{e.backtrace.join("\n\t")}"
49 | @connect_status.update_attributes!(
50 | status: :error,
51 | error_message: e.message
52 | )
53 | end
54 |
55 | private
56 |
57 | def current_user
58 | @connect_status.user
59 | end
60 |
61 | def oauth_code
62 | @connect_status.oauth_code
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/app/mailers/.keep
--------------------------------------------------------------------------------
/app/mailers/user_mailer.rb:
--------------------------------------------------------------------------------
1 | class UserMailer < ActionMailer::Base
2 | def access_revoked(user, github_user)
3 | @user = user
4 | @github_user = github_user
5 | mail(to: @user.email, subject: 'GitHub Access Revoked')
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/app/models/.keep
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/concerns/encryptable.rb:
--------------------------------------------------------------------------------
1 | module Encryptable
2 | extend ActiveSupport::Concern
3 |
4 | # The encrypted database salt environment variable.
5 | ENCRYPTED_DATABASE_SALT = 'encryptable.encrypted_database_salt'.freeze
6 |
7 | module ClassMethods
8 | def attr_encryptor(attr)
9 | field = "encrypted_#{attr}"
10 | define_method("#{attr}=") { |val|
11 | unless val == self.send("#{attr}")
12 | self.send("#{field}=", encrypt(val))
13 | end
14 | }
15 | define_method("#{attr}") { decrypt(self.send(field)) }
16 | end
17 |
18 | def crypt
19 | @crypt ||= begin
20 | salt = ENV[ENCRYPTED_DATABASE_SALT] || ''
21 | key_generator = ActiveSupport::KeyGenerator.new(Rails.application.secrets.database_key, iterations: 2000)
22 | key = key_generator.generate_key(salt)
23 | ActiveSupport::MessageEncryptor.new(key)
24 | end
25 | end
26 | end
27 |
28 | def encrypt(data)
29 | return nil if data.nil?
30 | self.class.crypt.encrypt_and_sign(data)
31 | end
32 |
33 | def decrypt(data)
34 | return nil if data.nil?
35 | self.class.crypt.decrypt_and_verify(data)
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/connect_github_user_status.rb:
--------------------------------------------------------------------------------
1 | class ConnectGithubUserStatus < ActiveRecord::Base
2 | belongs_to :user
3 | belongs_to :github_user
4 |
5 | def step_complete?(step)
6 | steps_completed.include?(step)
7 | end
8 |
9 | def step_disabled?(step)
10 | steps_disabled.include?(step)
11 | end
12 |
13 | def step_error?(step)
14 | self.step == step && status == :error
15 | end
16 |
17 | def in_progress?
18 | %i(queued running).include?(status)
19 | end
20 |
21 | def complete?
22 | %i(complete).include?(status)
23 | end
24 |
25 | def status
26 | status = read_attribute(:status)
27 | status ? status.to_sym : nil
28 | end
29 |
30 | def step
31 | step = read_attribute(:step)
32 | step ? step.to_sym : nil
33 | end
34 |
35 | def steps
36 | %i(create request grant add teams)
37 | end
38 |
39 | def steps_completed
40 | if step == :request && !github_user_id
41 | []
42 | elsif status == :complete
43 | steps
44 | else
45 | steps.first(step_index)
46 | end
47 | end
48 |
49 | def steps_disabled
50 | steps.last(steps.count - step_index - 1)
51 | end
52 |
53 | private
54 |
55 | def step_index
56 | steps.index(step)
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/app/models/github_email.rb:
--------------------------------------------------------------------------------
1 | class GithubEmail < ActiveRecord::Base
2 | belongs_to :github_user
3 |
4 | default_scope { order(:created_at) }
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/github_organization_membership.rb:
--------------------------------------------------------------------------------
1 | class GithubOrganizationMembership < ActiveRecord::Base
2 | belongs_to :github_user
3 |
4 | default_scope { order(:created_at) }
5 |
6 | def admin?
7 | role == 'admin'
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/models/github_team.rb:
--------------------------------------------------------------------------------
1 | class GithubTeam < ActiveRecord::Base
2 | has_and_belongs_to_many :github_users, join_table: :github_user_teams
3 |
4 | attr_accessor :github_admin
5 |
6 | # Finds a GithubTeam using a "full" slug. A full slug
7 | # is the organization and team slug combined with a slash, for example:
8 | # org1/myteam
9 | #
10 | # @param full_slug [String] an organization and team slug separated with a slash
11 | # @return [GithubTeam]
12 | def self.find_by_full_slug(full_slug)
13 | (org, slug) = full_slug.split('/', 2)
14 | where(organization: org, slug: slug).first
15 | end
16 |
17 | # Does this team allow external users?
18 | #
19 | # @return [Boolean]
20 | # @see {GithubConnector::Settings#github_external_users}
21 | def external?
22 | external_teams = Rails.application.settings.github_external_teams
23 | external_teams && (external_teams.include?(slug) || external_teams.include?(full_slug))
24 | end
25 |
26 | # Returns the "full" slug for this team. A full slug
27 | # is the organization and team slug combined with a slash, for example:
28 | # org1/myteam
29 | #
30 | # @return [String] an organization and team slug separated with a slash
31 | def full_slug
32 | "#{organization}/#{slug}"
33 | end
34 |
35 | def github_admin
36 | @github_admin ||= GithubAdmin.new
37 | end
38 |
39 | # Synchronizes {GithubTeam} attributes and members from Github.
40 | #
41 | # @return [Boolean] true if saved successfully. NOTE: This method returns
42 | # true even if GitHub API errors occur, as long as the error is successfully
43 | # saved to the `sync_error` attribute.
44 | def sync
45 | # TODO: Handle errors
46 | sync_github_team & sync_github_members
47 | end
48 |
49 | # Synchronizes {GithubTeam} attributes and members from GitHub.
50 | # An `ActiveRecord::RecordNotSaved` error is raised if the save
51 | # fails.
52 | #
53 | # @return [void]
54 | def sync!
55 | sync || raise(ActiveRecord::RecordNotSaved)
56 | end
57 |
58 | protected
59 |
60 | def sync_github_team
61 | data = github_admin.team(id)
62 | self.id = data[:id]
63 | self.name = data[:name]
64 | self.organization = data[:organization]
65 | self.slug = data[:slug]
66 | if changed?
67 | save
68 | else
69 | true
70 | end
71 | end
72 |
73 | def sync_github_members
74 | members = github_admin.team_members(id)
75 | added_members = []
76 | removed_users = []
77 |
78 | github_users.each do |user|
79 | next if members.has_key?(user.login)
80 | # TODO: Don't remove disabled users???
81 | removed_users << user
82 | end
83 | github_users.delete(*removed_users) unless removed_users.empty?
84 |
85 | members.each do |login, member|
86 | next if github_users.any? { |user| user.login == login }
87 | added_members << login
88 | end
89 | github_users << GithubUser.where(login: added_members) unless added_members.empty?
90 |
91 | true
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/app/models/setting.rb:
--------------------------------------------------------------------------------
1 | class Setting < ActiveRecord::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/connect/_connect_step.html.erb:
--------------------------------------------------------------------------------
1 | <%
2 | classes = %w(list-group-item step-request)
3 | classes << 'disabled' if @connect_status.step_disabled?(step)
4 | if @connect_status.step_error?(step)
5 | classes << 'error'
6 | else
7 | classes << 'complete' if @connect_status.step_complete?(step)
8 | classes << 'active' if @connect_status.step == step
9 | end
10 | %>
11 |
12 |
13 |
14 |
15 |
16 |
17 | <%= yield %>
18 |
19 |
--------------------------------------------------------------------------------
/app/views/dashboard/index.html.erb:
--------------------------------------------------------------------------------
1 | <% jumbotron do %>
2 | GitHub Connector
3 | Connect your GitHub.com account(s) to <%= settings.company.present? ? settings.company : "Active Directory" %>
4 | <% if current_user.github_users.empty? %>
5 | <%= link_to("Connect your GitHub.com account", connect_path, class: "btn btn-default btn-lg") %>
6 | <% else %>
7 | <%= link_to("Add another GitHub.com account", connect_path, class: "btn btn-default btn-lg") %>
8 | <% end %>
9 | <% end %>
10 |
11 |
12 | <%= render partial: 'users/github_users', locals: {user: current_user} %>
13 |
14 | <% if current_user.github_users.empty? %>
15 | <%= link_to('Connect your GitHub Account', connect_path, class: 'btn btn-default') %>
16 | <% else %>
17 | <%= link_to('Add another GitHub Account', connect_path, class: 'btn btn-default') %>
18 | <% end %>
19 |
--------------------------------------------------------------------------------
/app/views/devise/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Sign In" %>
2 | <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: {class: 'form-signin'}) do |f| %>
3 |
4 | Sign in using your active directory credentials.
5 |
6 | <%= f.text_field :username,
7 | autofocus: true,
8 | placeholder: "Username or email address",
9 | required: true,
10 | class: 'form-control' %>
11 | <%= f.password_field :password,
12 | autocomplete: 'off',
13 | placeholder: "Password",
14 | class: 'form-control' %>
15 |
16 | <% if devise_mapping.rememberable? -%>
17 |
18 | <%= f.label :remember_me do %>
19 | <%= f.check_box :remember_me %>Remember me?
20 | <% end %>
21 |
22 | <% end -%>
23 |
24 | <%= f.submit "Sign in", class: 'btn btn-lg btn-primary btn-block' %>
25 | <% end %>
26 |
27 | <%= render "devise/shared/links" %>
28 |
--------------------------------------------------------------------------------
/app/views/devise/shared/_links.erb:
--------------------------------------------------------------------------------
1 | <%- if controller_name != 'sessions' %>
2 | <%= link_to "Sign in", new_session_path(resource_name) %>
3 | <% end -%>
4 |
5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%>
8 |
9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%>
12 |
13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%>
16 |
17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%>
20 |
21 | <%- if devise_mapping.omniauthable? %>
22 | <%- resource_class.omniauth_providers.each do |provider| %>
23 | <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%>
25 | <% end -%>
26 |
--------------------------------------------------------------------------------
/app/views/github_users/index.html.erb:
--------------------------------------------------------------------------------
1 | <% title "GitHub Users" %>
2 | <% nav_section :github_users %>
3 | <% jumbotron do %>
4 | GitHub Users
5 | GitHub users associated with <%= settings.company.present? ? settings.company : 'our' %> GitHub organizations
6 | <% end %>
7 |
8 |
9 |
10 |
11 | GitHub Account
12 | Active Directory User
13 | State
14 |
15 |
16 |
17 | <% @github_users.each do |ghuser| %>
18 |
19 | <%= link_to(ghuser.login, ghuser) %>
20 |
21 | <% if ghuser.user %>
22 | <%= link_to(ghuser.user.name, ghuser.user) %>
23 | <% end %>
24 |
25 | <%= github_user_state_label(ghuser) %>
26 |
27 | <% end %>
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/views/github_users/show.html.erb:
--------------------------------------------------------------------------------
1 | <% title @github_user.login %>
2 | <% nav_section :github_users %>
3 | <% jumbotron do %>
4 | <%= @github_user.login %>
5 | <% end %>
6 |
7 | <%= render partial: 'users/github_user', locals: {github_user: @github_user} %>
8 |
--------------------------------------------------------------------------------
/app/views/settings/_active_directory.html.erb:
--------------------------------------------------------------------------------
1 |
8 |
9 |
20 |
21 |
28 |
29 |
38 |
39 |
46 |
47 |
54 |
55 |
--------------------------------------------------------------------------------
/app/views/settings/_company.html.erb:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/app/views/settings/_github.html.erb:
--------------------------------------------------------------------------------
1 |
8 |
9 |
16 |
17 |
32 |
33 |
43 |
44 |
51 |
52 |
--------------------------------------------------------------------------------
/app/views/settings/_rules.html.erb:
--------------------------------------------------------------------------------
1 |
12 |
13 |
23 |
24 |
31 |
32 |
42 |
43 |
53 |
54 |
64 |
--------------------------------------------------------------------------------
/app/views/settings/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Settings" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Settings
5 | Active Directory, GitHub and application settings
6 | <% end %>
7 |
8 | <%= form_tag({action: :update}, method: :put, class: 'form-horizontal max-col-sm') do %>
9 | <% if @error %>
10 | <%= @error %>
11 | <% end %>
12 |
13 | <%= render 'company' %>
14 |
15 | <% @section_partials.each do |header, partial| %>
16 | <%= header %>
17 | <%= render partial %>
18 | <% end %>
19 |
20 |
25 | <% end %>
26 |
--------------------------------------------------------------------------------
/app/views/setup/admin_user/new.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Create admin user - Setup Wizard" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Setup Wizard
5 | Step 3: Create initial admin user
6 | <% end %>
7 |
8 |
9 | Create the initial admin user by signing in with your Active Directory credentials. This user will become an administrator for the GitHub Connector.
10 |
11 | <%= form_for(resource, as: resource_name, url: setup_admin_path, html: {class: 'form-signin left'}) do |f| %>
12 |
13 | <%= f.text_field :username,
14 | autofocus: true,
15 | placeholder: "Username or email address",
16 | required: true,
17 | class: 'form-control' %>
18 | <%= f.password_field :password,
19 | autocomplete: 'off',
20 | placeholder: "Password",
21 | class: 'form-control' %>
22 |
23 | <%= f.submit "Create admin user", class: 'btn btn-lg btn-primary btn-block' %>
24 | <% end %>
25 |
--------------------------------------------------------------------------------
/app/views/setup/company/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Setup Wizard" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Setup Wizard
5 | Step 1: Company settings
6 | <% end %>
7 |
8 | Let's get started configuring the Github Connector.
9 |
10 | This wizard will guide you through configuring the Github Connector. Along the way, we'll make some educated guesses for defaults, but please review each configuration option as you go. You may always update settings after completing the wizard.
11 |
12 | First, enter some information about your company below.
13 |
14 | <%= form_tag({action: :update}, method: :put, class: 'form-horizontal max-col-sm') do %>
15 | <% if @error %>
16 | <%= @error %>
17 | <% end %>
18 |
19 | Company
20 | <%= render 'settings/company' %>
21 |
22 |
27 | <% end %>
28 |
--------------------------------------------------------------------------------
/app/views/setup/email/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Setup Wizard" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Setup Wizard
5 | Step 5: Email settings
6 | <% end %>
7 |
8 | The Github Connector sends emails when disabling Github access. Configure your SMTP settings below.
9 |
10 | <%= form_tag({action: :update}, method: :put, class: 'form-horizontal max-col-sm') do %>
11 | <% if @error %>
12 | <%= @error %>
13 | <% end %>
14 |
15 | Email
16 | <%= render 'settings/email' %>
17 |
18 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/app/views/setup/github/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Setup Wizard" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Setup Wizard
5 | Step 4: Github settings
6 | <% end %>
7 |
8 | Enter information about your Github.com organization(s) below.
9 |
10 | <%= form_tag({action: :update}, method: :put, class: 'form-horizontal max-col-sm') do %>
11 | <% if @error %>
12 | <%= @error %>
13 | <% end %>
14 |
15 | Github
16 | <%= render 'settings/github' %>
17 |
18 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/app/views/setup/ldap/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Setup Wizard" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Setup Wizard
5 | Step 2: Active Directory settings
6 | <% end %>
7 |
8 | The Github Connector connects to Active Directory using a service account to read information about employees. Enter your Active Directory settings below.
9 |
10 | <%= form_tag({action: :update}, method: :put, class: 'form-horizontal max-col-sm') do %>
11 | <% if @error %>
12 | <%= @error %>
13 | <% end %>
14 |
15 | Active Directory
16 | <%= render 'settings/active_directory' %>
17 |
18 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/app/views/setup/rules/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Setup Wizard" %>
2 | <% nav_section :settings %>
3 | <% jumbotron do %>
4 | Setup Wizard
5 | Step 6: Rules
6 | <% end %>
7 |
8 | The Github Connector decides who can access Github.com based on a list of rules. Configure the rules below.
9 |
10 | <%= form_tag({action: :update}, method: :put, class: 'form-horizontal max-col-sm') do %>
11 | <% if @error %>
12 | <%= @error %>
13 | <% end %>
14 |
15 | Rules
16 | <%= render 'settings/rules' %>
17 |
18 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/app/views/user_mailer/access_revoked.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | GitHub access revoked!
8 |
9 | Your <%= Rails.application.settings.company %> GitHub access has
10 | been revoked due to the following <%= "error".pluralize(@github_user.failing_rules.count) %>:
11 |
12 |
13 | <% @github_user.failing_rules.each do |rule| %>
14 | <%= rule.error_msg %>
15 | <% end %>
16 |
17 |
18 |
19 | Please correct the <%= "error".pluralize(@github_user.failing_rules.count) %>
20 | and reconnect your GitHub account using this link:
21 | <%= link_to(connect_url, connect_url) %>
22 |
23 |
24 | This message was sent from an automated mailbox. Please do not reply.
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/views/user_mailer/access_revoked.text.erb:
--------------------------------------------------------------------------------
1 | GitHub access revoked!
2 | ======================
3 |
4 | Your <%= Rails.application.settings.company %> GitHub access has been revoked
5 | due to the following <%= "error".pluralize(@github_user.failing_rules.count) %>:
6 |
7 | <% @github_user.failing_rules.each do |rule| -%>
8 | * <%= rule.error_msg %>
9 | <% end -%>
10 |
11 | Please correct the <%= "error".pluralize(@github_user.failing_rules.count) %> and reconnect your GitHub
12 | account using this link:
13 | <%= connect_url %>
14 |
15 |
16 | ---
17 | This message was sent from an automated mailbox. Please do not reply.
18 |
--------------------------------------------------------------------------------
/app/views/users/_github_user.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= link_to github_user.html_url do %>
5 | <%= image_tag(github_user.avatar_url, height: 32, width: 32) %>
6 | <%= github_user.login %>
7 | <% end %>
8 |
9 |
10 |
11 |
12 | State
13 | <%= github_user_state_label(github_user) %>
14 |
15 |
16 |
17 | <%= 'Email'.pluralize(github_user.emails.size) %>
18 |
19 |
20 | <% github_user.emails.each do |email| %>
21 | <%= email.address %>
22 | <% end %>
23 |
24 |
25 |
26 |
27 |
28 | 2FA
29 | <%= github_user.mfa ? "Yes" : "No" %>
30 |
31 |
32 | <% unless github_user.org_memberships.empty? %>
33 |
34 | Organizations
35 |
36 |
41 |
42 | <% end %>
43 |
44 |
45 | <%= 'Team'.pluralize(github_user.teams.size) %>
46 |
47 |
48 | <% github_user.teams.each do |team| %>
49 | <%= link_to team.full_slug, "https://github.com/orgs/#{team.organization}/teams/#{team.slug}/repositories" %>
50 | <% end %>
51 |
52 |
53 |
54 |
55 |
56 | Last sync
57 | <%= format_time(github_user.last_sync_at) %>
58 |
59 |
60 | <% if github_user.sync_error %>
61 |
62 | Sync error
63 |
64 | <%= github_user.sync_error %>
65 |
66 | <%= format_time(github_user.sync_error_at) %>
67 |
68 |
69 | <% end %>
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/views/users/_github_users.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= 'GitHub Account'.pluralize(user.github_users.size) %>
3 | <% if user.github_users.empty? %>
4 | No linked GitHub accounts.
5 | <% else %>
6 | <%= render partial: 'users/github_user', collection: user.github_users.order(updated_at: :desc) %>
7 | <% end %>
8 |
9 |
--------------------------------------------------------------------------------
/app/views/users/_ldap_user.html.erb:
--------------------------------------------------------------------------------
1 |
2 | Active Directory
3 |
4 |
5 |
6 | Username
7 | <%= user.username %>
8 |
9 |
10 | Email
11 | <%= @user.email %>
12 |
13 |
14 | Flags
15 | <%= @user.ldap_account_control_flags.map {|flag| flag.to_s.humanize}.join(', ') %>
16 |
17 |
18 | Last sync
19 | <%= format_time(@user.last_ldap_sync) %>
20 |
21 | <% if @user.ldap_sync_error %>
22 |
23 | Sync error
24 |
25 | <%= @user.ldap_sync_error %>
26 |
27 | <%= format_time(@user.ldap_sync_error_at) %>
28 |
29 |
30 | <% end %>
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/views/users/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% title @user.name %>
2 | <% jumbotron do %>
3 | <%= @user.name %>
4 | <% end %>
5 |
6 | <%= form_for :user,
7 | url: user_path(@user),
8 | method: :patch,
9 | html: {
10 | class: 'form-horizontal max-col-sm'
11 | } do |f| %>
12 |
13 |
20 |
21 |
27 |
28 | <% end %>
29 |
--------------------------------------------------------------------------------
/app/views/users/index.html.erb:
--------------------------------------------------------------------------------
1 | <% title "Users" %>
2 | <% nav_section :users %>
3 | <% jumbotron do %>
4 | Users
5 | Active Directory users who have logged in to the GitHub Connector
6 | <% end %>
7 |
8 | <%
9 | # Users can have more than one GitHub user, which we want to display like:
10 | #
11 | # LDAP User | GitHub User | State
12 | # -----------+---------------+---------
13 | # User 1 | GH User 1a | Enabled
14 | # | GH User 1b | Enabled
15 | # User 2 | GH User 2 | Enabled
16 | # User 3 | |
17 | %>
18 |
19 |
20 |
21 | User
22 | GitHub Account
23 | State
24 |
25 |
26 | <% @users.each do |user| %>
27 | <% github_users = user.github_users.empty? ? [nil] : user.github_users %>
28 | <% github_users.each_with_index do |ghuser, i| %>
29 |
30 | <% if i == 0 %>
31 |
32 | <%= link_to(user.name, user) %>
33 |
34 | <% end %>
35 | <% if ghuser %>
36 | <%= link_to ghuser.login, user_path(user, anchor: ghuser.login) %>
37 | <%= github_user_state_label(ghuser) %>
38 | <% else %>
39 | None
40 | <% end %>
41 |
42 | <% end %>
43 | <% end %>
44 |
45 |
--------------------------------------------------------------------------------
/app/views/users/show.html.erb:
--------------------------------------------------------------------------------
1 | <% title @user.name %>
2 | <% nav_section :user if @user == current_user %>
3 | <% jumbotron do %>
4 | <%= @user.name %>
5 | <% end %>
6 |
7 | <%= render partial: 'users/ldap_user', locals: {user: @user} %>
8 | <%= render partial: 'users/github_users', locals: {user: @user} %>
9 |
10 | Internal
11 |
12 |
13 |
14 | Admin
15 | <%= @user.admin? ? 'Yes' : 'No' %>
16 |
17 |
18 |
19 | <% if current_user.admin? %>
20 | <%= link_to "Edit", edit_user_path(@user), class: 'btn btn-default' %>
21 | <% end %>
22 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/bin/delayed_job:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
4 | require 'delayed/command'
5 | Delayed::Command.new(ARGV).daemonize
6 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 |
4 | # path to your application root.
5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6 |
7 | Dir.chdir APP_ROOT do
8 | # This script is a starting point to setup your application.
9 | # Add necessary setup steps to this file:
10 |
11 | puts "== Installing dependencies =="
12 | system "gem install bundler --conservative"
13 | system "bundle check || bundle install"
14 |
15 | # puts "\n== Copying sample files =="
16 | # unless File.exist?("config/database.yml")
17 | # system "cp config/database.yml.sample config/database.yml"
18 | # end
19 |
20 | puts "\n== Preparing database =="
21 | system "bin/rake db:setup"
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system "rm -f log/*"
25 | system "rm -rf tmp/cache"
26 |
27 | puts "\n== Restarting application server =="
28 | system "touch tmp/restart.txt"
29 | end
30 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast
4 | # It gets overwritten when you run the `spring binstub` command
5 |
6 | unless defined?(Spring)
7 | require "rubygems"
8 | require "bundler"
9 |
10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
12 | ENV["GEM_HOME"] = ""
13 | Gem.paths = ENV
14 |
15 | gem "spring", match[1]
16 | require "spring/binstub"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module GithubConnector
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 |
15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
17 | # config.time_zone = 'Central Time (US & Canada)'
18 |
19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
21 | # config.i18n.default_locale = :de
22 |
23 | # For not swallow errors in after_commit/after_rollback callbacks.
24 | config.active_record.raise_in_transactional_callbacks = true
25 |
26 | config.active_job.queue_adapter = :delayed_job
27 |
28 | config.autoload_paths << Rails.root.join('lib')
29 |
30 | def settings
31 | @settings ||= Settings.new
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/config/database.yml.example:
--------------------------------------------------------------------------------
1 | development: &default
2 | adapter: postgresql
3 | database: github_connector
4 | pool: 5
5 | timeout: 5
6 |
7 | production:
8 | <<: *default
9 |
10 | # Warning: The database defined as "test" will be erased and
11 | # re-generated from your development database when you run "rake".
12 | # Do not set this db to the same as development or production.
13 | test:
14 | <<: *default
15 | database: github_connector_test
16 |
--------------------------------------------------------------------------------
/config/database.yml.travis:
--------------------------------------------------------------------------------
1 | development: &default
2 | adapter: postgresql
3 | database: github_connector
4 | username: postgres
5 | pool: 5
6 | timeout: 5
7 |
8 | production:
9 | <<: *default
10 |
11 | # Warning: The database defined as "test" will be erased and
12 | # re-generated from your development database when you run "rake".
13 | # Do not set this db to the same as development or production.
14 | test:
15 | <<: *default
16 | database: github_connector_test
17 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | #config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31 | # yet still be able to expire them through the digest params.
32 | config.assets.digest = true
33 |
34 | # Adds additional error checking when serving assets at runtime.
35 | # Checks for improperly declared sprockets dependencies.
36 | # Raises helpful error messages.
37 | config.assets.raise_runtime_errors = true
38 |
39 | # Raises error for missing translations
40 | # config.action_view.raise_on_missing_translations = true
41 | end
42 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 |
37 | # Raises error for missing translations
38 | # config.action_view.raise_on_missing_translations = true
39 | end
40 |
--------------------------------------------------------------------------------
/config/initializers/action_mailer.rb:
--------------------------------------------------------------------------------
1 | module ActionMailer
2 | class Base
3 |
4 | # Read mailer configuration settings from the database every time
5 | # we instantiate a new mailer.
6 | def initialize_with_config(*args)
7 | Rails.application.settings.apply_to_action_mailer
8 | initialize_without_config(*args)
9 | end
10 | alias_method_chain :initialize, :config
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
4 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_github_connector_session'
4 |
--------------------------------------------------------------------------------
/config/initializers/state_machine_patch.rb:
--------------------------------------------------------------------------------
1 | # The state_machine gem doesn't support Rails 4.1 out of the box.
2 | # This patches stuff to work.
3 | #
4 | # See: https://github.com/pluginaweek/state_machine/issues/251
5 | module StateMachine
6 | module Integrations
7 | module ActiveModel
8 | public :around_validation
9 | end
10 |
11 | module ActiveRecord
12 | public :around_save
13 | end
14 | end
15 | end
16 | module StateMachine
17 | module Integrations
18 | module ActiveModel
19 | public :around_validation
20 | end
21 |
22 | module ActiveRecord
23 | public :around_save
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | get 'settings', to: 'settings#edit'
3 | put 'settings', to: 'settings#update'
4 | get 'settings/github_admin', to: 'settings#github_admin'
5 | get 'settings/github_auth_code', to: 'settings#github_auth_code'
6 |
7 | get 'setup', to: redirect('setup/company')
8 | namespace :setup do
9 | # Step 1
10 | get 'company', to: 'company#edit'
11 | put 'company', to: 'company#update'
12 | # Step 2
13 | get 'ldap', to: 'ldap#edit'
14 | put 'ldap', to: 'ldap#update'
15 | # Step 3
16 | devise_scope :user do
17 | get 'admin', to: 'admin_user#new'
18 | post 'admin', to: 'admin_user#create'
19 | end
20 | # Step 4
21 | get 'github', to: 'github#edit'
22 | get 'github_auth_code', to: 'github#github_auth_code'
23 | put 'github', to: 'github#update'
24 | # Step 5
25 | get 'email', to: 'email#edit'
26 | put 'email', to: 'email#update'
27 | # Step 6
28 | get 'rules', to: 'rules#edit'
29 | put 'rules', to: 'rules#update'
30 | end
31 |
32 | get 'connect', to: 'connect#index'
33 | get 'connect/start', to: 'connect#start'
34 | get 'connect/auth_code', to: 'connect#auth_code'
35 | get 'connect/:id', to: 'connect#status', as: 'connect_status', constraints: { id: /\d+/ }
36 |
37 | devise_for :users
38 |
39 | resources :users, only: [:index, :show, :edit, :update], constraints: { id: /[^\/]+?/ }
40 | resources :github_users, only: [:index, :show]
41 |
42 | root 'dashboard#index'
43 | end
44 |
--------------------------------------------------------------------------------
/config/secrets.yml.example:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # Make sure the secret is at least 30 characters and all random,
4 | # no regular words or you'll be exposed to dictionary attacks.
5 | # You can use `rake secret` to generate a secure secret key.
6 | #
7 | # Make sure the secrets in this file are kept private
8 | # if you're sharing your code publicly.
9 |
10 | development: &default
11 | # Your secret key is used for verifying the integrity of signed cookies.
12 | # If you change this key, all old signed cookies will become invalid!
13 | secret_key_base: GENERATE A SECRET WITH rake secret AND PASTE IT HERE
14 |
15 | # The secret key used for encrypting sensitive database info.
16 | # If you change this key, all user OAuth tokens will become unreadable!
17 | database_key: GENERATE A SECRET WITH rake secret AND PASTE IT HERE
18 |
19 | production:
20 | <<: *default
21 |
22 | test:
23 | <<: *default
24 |
--------------------------------------------------------------------------------
/config/secrets.yml.travis:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # Make sure the secret is at least 30 characters and all random,
4 | # no regular words or you'll be exposed to dictionary attacks.
5 | # You can use `rake secret` to generate a secure secret key.
6 | #
7 | # Make sure the secrets in this file are kept private
8 | # if you're sharing your code publicly.
9 |
10 | development: &default
11 | # Your secret key is used for verifying the integrity of signed cookies.
12 | # If you change this key, all old signed cookies will become invalid!
13 | secret_key_base: ThisIsANot-So-SecretKeyForTravisFOO
14 |
15 | # The secret key used for encrypting sensitive database info.
16 | # If you change this key, all user OAuth tokens will become unreadable!
17 | database_key: ThisIsANot-So-SecretKeyForTravisBAR
18 |
19 | production:
20 | <<: *default
21 |
22 | test:
23 | <<: *default
24 |
--------------------------------------------------------------------------------
/cookbook/.kitchen.yml:
--------------------------------------------------------------------------------
1 | ---
2 | driver:
3 | name: vagrant
4 | customize:
5 | memory: 1024
6 | # Use the host's DNS. This ensures everything resolves correctly
7 | # inside the guest when the host is connected to VPN.
8 | natdnshostresolver1: 'on'
9 |
10 | provisioner:
11 | name: chef_solo
12 |
13 | platforms:
14 | - name: ubuntu-14.04
15 |
16 | suites:
17 | - name: default
18 | driver:
19 | network:
20 | - [forwarded_port, {guest: 8080, host: 8080}]
21 | - [forwarded_port, {guest: 8443, host: 8443}]
22 | data_bags_path: test_data_bags
23 | run_list:
24 | - recipe[github_connector::default]
25 | attributes:
26 | authorization:
27 | sudo:
28 | users: [vagrant]
29 | agent_forwarding: true
30 | postgresql:
31 | password:
32 | postgres: insecurepassword
33 | github_connector:
34 | http:
35 | port: 8080
36 | ssl:
37 | port: 8443
38 | enabled: true
39 |
--------------------------------------------------------------------------------
/cookbook/Berksfile:
--------------------------------------------------------------------------------
1 | source "https://supermarket.getchef.com"
2 |
3 | # Can switch to upstream once these land:
4 | # https://github.com/fnichol/chef-rvm/pull/186
5 | # https://github.com/fnichol/chef-rvm/pull/212
6 | # https://github.com/fnichol/chef-rvm/pull/247
7 | cookbook 'rvm', '>= 0.9.0', git: 'git://github.com/rapid7-cookbooks/rvm.git', branch: 'patches_0.9.0'
8 |
9 | metadata
10 |
11 |
--------------------------------------------------------------------------------
/cookbook/Berksfile.lock:
--------------------------------------------------------------------------------
1 | DEPENDENCIES
2 | github_connector
3 | path: .
4 | metadata: true
5 | rvm
6 | git: git://github.com/rapid7-cookbooks/rvm.git
7 | revision: 19a74ea6e1fcb2c021ef016ce0e482a04a71c108
8 | branch: patches_0.9.0
9 |
10 | GRAPH
11 | apt (2.7.0)
12 | bluepill (2.3.1)
13 | rsyslog (>= 0.0.0)
14 | build-essential (2.2.2)
15 | chef-sugar (3.1.0)
16 | chef_gem (0.1.0)
17 | database (4.0.3)
18 | postgresql (>= 1.0.0)
19 | github_connector (0.1.6)
20 | apt (>= 2.3.10)
21 | database (>= 2.0)
22 | logrotate (>= 1.7.0)
23 | nginx (>= 2.0)
24 | postgresql (~> 3.4)
25 | rvm (= 0.9.0)
26 | ssh_known_hosts (>= 0.0.0)
27 | logrotate (1.9.1)
28 | nginx (2.7.6)
29 | apt (~> 2.2)
30 | bluepill (~> 2.3)
31 | build-essential (~> 2.0)
32 | ohai (~> 2.0)
33 | runit (~> 1.2)
34 | yum-epel (~> 0.3)
35 | ohai (2.0.1)
36 | openssl (4.0.0)
37 | chef-sugar (>= 0.0.0)
38 | partial_search (1.0.8)
39 | postgresql (3.4.18)
40 | apt (>= 1.9.0)
41 | build-essential (>= 0.0.0)
42 | openssl (~> 4.0.0)
43 | rsyslog (1.15.0)
44 | runit (1.5.18)
45 | build-essential (>= 0.0.0)
46 | yum (~> 3.0)
47 | yum-epel (>= 0.0.0)
48 | rvm (0.9.0)
49 | chef_gem (>= 0.0.0)
50 | ssh_known_hosts (2.0.0)
51 | partial_search (>= 0.0.0)
52 | yum (3.5.3)
53 | yum-epel (0.6.0)
54 | yum (~> 3.0)
55 |
--------------------------------------------------------------------------------
/cookbook/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'berkshelf'
4 | gem 'kitchen-vagrant'
5 | gem 'test-kitchen'
6 |
--------------------------------------------------------------------------------
/cookbook/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | addressable (2.3.8)
5 | berkshelf (4.0.1)
6 | addressable (~> 2.3.4)
7 | berkshelf-api-client (~> 2.0)
8 | buff-config (~> 1.0)
9 | buff-extensions (~> 1.0)
10 | buff-shell_out (~> 0.1)
11 | celluloid (= 0.16.0)
12 | celluloid-io (~> 0.16.1)
13 | cleanroom (~> 1.0)
14 | faraday (~> 0.9.0)
15 | httpclient (~> 2.6.0)
16 | minitar (~> 0.5.4)
17 | octokit (~> 3.0)
18 | retryable (~> 2.0)
19 | ridley (~> 4.3)
20 | solve (~> 1.1)
21 | thor (~> 0.19)
22 | berkshelf-api-client (2.0.0)
23 | faraday (~> 0.9.1)
24 | httpclient (~> 2.6.0)
25 | buff-config (1.0.1)
26 | buff-extensions (~> 1.0)
27 | varia_model (~> 0.4)
28 | buff-extensions (1.0.0)
29 | buff-ignore (1.1.1)
30 | buff-ruby_engine (0.1.0)
31 | buff-shell_out (0.2.0)
32 | buff-ruby_engine (~> 0.1.0)
33 | celluloid (0.16.0)
34 | timers (~> 4.0.0)
35 | celluloid-io (0.16.2)
36 | celluloid (>= 0.16.0)
37 | nio4r (>= 1.1.0)
38 | chef-config (12.6.0)
39 | mixlib-config (~> 2.0)
40 | mixlib-shellout (~> 2.0)
41 | cleanroom (1.0.0)
42 | dep-selector-libgecode (1.0.2)
43 | dep_selector (1.0.3)
44 | dep-selector-libgecode (~> 1.0)
45 | ffi (~> 1.9)
46 | erubis (2.7.0)
47 | faraday (0.9.2)
48 | multipart-post (>= 1.2, < 3)
49 | ffi (1.9.10)
50 | hashie (3.4.3)
51 | hitimes (1.2.3)
52 | httpclient (2.6.0.1)
53 | json (1.8.3)
54 | kitchen-vagrant (0.19.0)
55 | test-kitchen (~> 1.4)
56 | minitar (0.5.4)
57 | mixlib-authentication (1.3.0)
58 | mixlib-log
59 | mixlib-config (2.2.1)
60 | mixlib-log (1.6.0)
61 | mixlib-shellout (2.2.5)
62 | multipart-post (2.0.0)
63 | net-scp (1.2.1)
64 | net-ssh (>= 2.6.5)
65 | net-ssh (2.9.2)
66 | nio4r (1.2.0)
67 | octokit (3.8.0)
68 | sawyer (~> 0.6.0, >= 0.5.3)
69 | retryable (2.0.3)
70 | ridley (4.4.2)
71 | addressable
72 | buff-config (~> 1.0)
73 | buff-extensions (~> 1.0)
74 | buff-ignore (~> 1.1)
75 | buff-shell_out (~> 0.1)
76 | celluloid (~> 0.16.0)
77 | celluloid-io (~> 0.16.1)
78 | chef-config
79 | erubis
80 | faraday (~> 0.9.0)
81 | hashie (>= 2.0.2, < 4.0.0)
82 | httpclient (~> 2.6)
83 | json (>= 1.7.7)
84 | mixlib-authentication (>= 1.3.0)
85 | retryable (~> 2.0)
86 | semverse (~> 1.1)
87 | varia_model (~> 0.4.0)
88 | safe_yaml (1.0.4)
89 | sawyer (0.6.0)
90 | addressable (~> 2.3.5)
91 | faraday (~> 0.8, < 0.10)
92 | semverse (1.2.1)
93 | solve (1.2.1)
94 | dep_selector (~> 1.0)
95 | semverse (~> 1.1)
96 | test-kitchen (1.4.2)
97 | mixlib-shellout (>= 1.2, < 3.0)
98 | net-scp (~> 1.1)
99 | net-ssh (~> 2.7, < 2.10)
100 | safe_yaml (~> 1.0)
101 | thor (~> 0.18)
102 | thor (0.19.1)
103 | timers (4.0.4)
104 | hitimes
105 | varia_model (0.4.1)
106 | buff-extensions (~> 1.0)
107 | hashie (>= 2.0.2, < 4.0.0)
108 |
109 | PLATFORMS
110 | ruby
111 |
112 | DEPENDENCIES
113 | berkshelf
114 | kitchen-vagrant
115 | test-kitchen
116 |
117 | BUNDLED WITH
118 | 1.11.2
119 |
--------------------------------------------------------------------------------
/cookbook/README.md:
--------------------------------------------------------------------------------
1 | GitHub Active Directory Connector Cookbook
2 | ==========================================
3 |
4 | Installs and configures the GitHub Active Directory Connector via Chef.
5 |
6 | This performs the following actions:
7 |
8 | 1. Creates a `github` user
9 | 2. Installs PostgreSQL and creates a database
10 | 3. Installs RVM, installs ruby, and configures a `github-connector` gemset
11 | 4. Clones the `github-connector` repository from GitHub
12 | 5. Creates upstart jobs for the web and worker processes
13 | 6. Creates a cron job to synchronize users
14 |
--------------------------------------------------------------------------------
/cookbook/attributes/database.rb:
--------------------------------------------------------------------------------
1 | default['github_connector']['db']['host'] = 'localhost'
2 | default['github_connector']['db']['port'] = node['postgresql']['config']['port']
3 | default['github_connector']['db']['name'] = 'github-connector'
4 | default['github_connector']['db']['user'] = 'github-connector'
5 |
--------------------------------------------------------------------------------
/cookbook/attributes/default.rb:
--------------------------------------------------------------------------------
1 | default['github_connector']['user'] = 'github'
2 | default['github_connector']['group'] = node['github_connector']['user']
3 | default['github_connector']['install_dir'] = '/var/www/github-connector'
4 |
5 | default['github_connector']['repo']['url'] = 'https://github.com/rapid7/github-connector.git'
6 | default['github_connector']['repo']['revision'] = 'v0.1.5'
7 |
8 | # The secrets databag can contain the following keys:
9 | # * database_password
10 | # * database_key
11 | # * secrets_key_base
12 | default['github_connector']['secrets_databag'] = 'github_connector'
13 | default['github_connector']['secrets_databag_item'] = 'secrets'
14 | default['github_connector']['secrets'] = {}
15 |
--------------------------------------------------------------------------------
/cookbook/attributes/engines.rb:
--------------------------------------------------------------------------------
1 | default['github_connector']['engines'] = {}
2 |
3 | # Install custom engines like this:
4 | #default['github_connector']['engines']['test_engine'] = {
5 | # 'url' => 'git@github.com:myorg/test_engine.git',
6 | # 'revision' => 'master',
7 | # 'ssh_databag' => 'ssh-keys',
8 | # 'ssh_databag_item => 'test_engine',
9 | #}
10 |
--------------------------------------------------------------------------------
/cookbook/attributes/nginx.rb:
--------------------------------------------------------------------------------
1 | default['nginx']['default_site_enabled'] = false
2 |
3 | default['github_connector']['http']['host_name'] = node['fqdn']
4 | default['github_connector']['http']['host_aliases'] = []
5 | default['github_connector']['http']['port'] = 80
6 | default['github_connector']['http']['ssl']['port'] = 443
7 | default['github_connector']['http']['ssl']['enabled'] = true
8 |
9 | # The cert databag should have `cert` and `key` keys
10 | default['github_connector']['http']['ssl']['cert_databag'] = 'github_connector'
11 | default['github_connector']['http']['ssl']['cert_databag_item'] = 'ssl_cert'
12 |
--------------------------------------------------------------------------------
/cookbook/attributes/ruby.rb:
--------------------------------------------------------------------------------
1 | default['github_connector']['ruby_version'] = 'ruby-2.3.0'
2 | default['github_connector']['ruby_gemset'] = 'github-connector'
3 | default['github_connector']['rvm_alias'] = 'github-connector'
4 |
5 | default['rvm']['version'] = '1.26.11'
6 | default['rvm']['user_rubies'] = [node['github_connector']['ruby_version']]
7 | default['rvm']['user_default_ruby'] = node['github_connector']['ruby_version']
8 | default['rvm']['user_autolibs'] = 'read-fail'
9 |
--------------------------------------------------------------------------------
/cookbook/attributes/ssh.rb:
--------------------------------------------------------------------------------
1 | # To pull from GitHub via ssh, add a data bag with a "private_key" attribute
2 | # containing an SSH private key. Then list the data bag info here:
3 | default['github_connector']['ssh_databag'] = nil
4 | default['github_connector']['ssh_databag_item'] = nil
5 |
6 |
7 | default['github_connector']['github_host_key'] = 'AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=='
8 |
--------------------------------------------------------------------------------
/cookbook/libraries/github_connector_helpers.rb:
--------------------------------------------------------------------------------
1 | module GithubConnector
2 | class Helpers
3 | class << self
4 | include Opscode::OpenSSL::Password
5 |
6 | # Loads the given data bag. The databag can be encrypted or unencrypted.
7 | def load_data_bag(data_bag, name)
8 | raw_hash = Chef::DataBagItem.load(data_bag, name)
9 | encrypted = raw_hash.detect do |key, value|
10 | if value.is_a?(Hash)
11 | value.has_key?("encrypted_data")
12 | end
13 | end
14 | if encrypted
15 | secret = Chef::EncryptedDataBagItem.load_secret
16 | Chef::EncryptedDataBagItem.new(raw_hash, secret)
17 | else
18 | raw_hash
19 | end
20 | end
21 |
22 | def database_password(node)
23 | secret('database_password', secure_password, node)
24 | end
25 |
26 | def database_key(node)
27 | secret('database_key', SecureRandom.hex(64), node)
28 | end
29 |
30 | def secret_key_base(node)
31 | secret('secret_key_base', SecureRandom.hex(64), node)
32 | end
33 |
34 | def secret(key, default, node)
35 | data_bag = GithubConnector::Helpers.load_data_bag(
36 | node['github_connector']['secrets_databag'],
37 | node['github_connector']['secrets_databag_item']
38 | ) rescue nil
39 |
40 | if data_bag && data_bag[key]
41 | return data_bag[key]
42 | end
43 |
44 | unless Chef::Config[:solo]
45 | node.set_unless['github_connector']['secrets'][key] = default
46 | node.save
47 | end
48 |
49 | raise "Must set github_connector.secrets.#{key}!" unless node['github_connector']['secrets'][key]
50 |
51 | node['github_connector']['secrets'][key]
52 | end
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/cookbook/metadata.rb:
--------------------------------------------------------------------------------
1 | name 'github_connector'
2 | maintainer "Rapid7, Inc."
3 | maintainer_email "engineeringservices@rapid7.com"
4 | license "Apache v2.0"
5 | description "Installs and configures the GitHub Active Directory Connector"
6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
7 | source_url "https://github.com/rapid7/github-connector/tree/master/cookbook"
8 | issues_url "https://github.com/rapid7/github-connector/issues"
9 | version "0.1.7"
10 |
11 | supports 'ubuntu'
12 |
13 | depends 'apt', '>= 2.3.10'
14 | depends 'database', '>= 2.0'
15 | depends 'logrotate', '>= 1.7.0'
16 | depends 'nginx', '>= 2.0'
17 | # postgres 4.0 cookbook introduces changes that haven't been tested.
18 | depends 'postgresql', '~> 3.4'
19 | depends 'ssh_known_hosts'
20 |
21 | # rvm is a rapid7 patched version, see Berksfile
22 | depends 'rvm', '= 0.9.0'
23 |
--------------------------------------------------------------------------------
/cookbook/recipes/cron.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: cron
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | cron 'github-connector-cron' do
21 | user node['github_connector']['user']
22 | minute 56
23 | hour '*/4'
24 | home "/home/#{node['github_connector']['user']}"
25 | command "cd \"#{node['github_connector']['install_dir']}\" && /home/#{node['github_connector']['user']}/.rvm/bin/rvm #{node['github_connector']['rvm_alias']} do rake github:transition_users RAILS_ENV=production >> \"#{node['github_connector']['install_dir']}/log/cron.log\""
26 | end
27 |
--------------------------------------------------------------------------------
/cookbook/recipes/database.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: database
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | include_recipe 'postgresql::server'
21 | include_recipe 'database::postgresql'
22 |
23 |
24 | postgresql_connection_info = {
25 | :host => "localhost",
26 | :port => node['postgresql']['config']['port'],
27 | :username => 'postgres',
28 | :password => node['postgresql']['password']['postgres']
29 | }
30 |
31 | # Create database user
32 | postgresql_database_user 'github-connector-database-user' do
33 | connection postgresql_connection_info
34 | username node['github_connector']['db']['user']
35 | password GithubConnector::Helpers.database_password(node)
36 | end
37 |
38 | # Create database
39 | postgresql_database 'github-connector-database' do
40 | connection postgresql_connection_info
41 | database_name node['github_connector']['db']['name']
42 | owner node['github_connector']['db']['user']
43 | end
44 |
--------------------------------------------------------------------------------
/cookbook/recipes/default.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: default
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | include_recipe 'apt'
21 |
22 | package 'git'
23 |
24 | include_recipe 'github_connector::user'
25 | include_recipe 'github_connector::ssh'
26 | include_recipe 'github_connector::database'
27 | include_recipe 'github_connector::ruby'
28 | include_recipe 'github_connector::server'
29 |
--------------------------------------------------------------------------------
/cookbook/recipes/nginx.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: nginx
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | include_recipe 'nginx'
21 |
22 | if node['github_connector']['http']['ssl']['enabled']
23 | ssl_data_bag = GithubConnector::Helpers.load_data_bag(
24 | node['github_connector']['http']['ssl']['cert_databag'],
25 | node['github_connector']['http']['ssl']['cert_databag_item']
26 | )
27 |
28 | # Public key.
29 | file "/etc/ssl/certs/#{node['github_connector']['http']['host_name']}.crt" do
30 | mode 0644
31 | user 'root'
32 | group 'root'
33 | content "#{ssl_data_bag['cert']}"
34 | notifies :reload, 'service[nginx]', :delayed
35 | end
36 |
37 | # Private key.
38 | file "/etc/ssl/private/#{node['github_connector']['http']['host_name']}.key" do
39 | mode 0600
40 | user 'root'
41 | group 'root'
42 | content "#{ssl_data_bag['key']}"
43 | notifies :reload, 'service[nginx]', :delayed
44 | end
45 | end
46 |
47 | template ::File.join(node['nginx']['dir'], 'sites-available', 'github_connector') do
48 | source 'nginx-github-connector.conf.erb'
49 | notifies :reload, 'service[nginx]', :delayed
50 | mode 0644
51 | owner 'root'
52 | group 'root'
53 | action :create
54 | variables(
55 | :host_name => node['github_connector']['http']['host_name'],
56 | :host_aliases => node['github_connector']['http']['host_aliases'] || [],
57 | :ssl_enabled => node['github_connector']['http']['ssl']['enabled'],
58 | :redirect_http => node['github_connector']['http']['ssl']['enabled'],
59 | :listen_port => node['github_connector']['http']['port'],
60 | :ssl_listen_port => node['github_connector']['http']['ssl']['port'],
61 | :install_dir => node['github_connector']['install_dir']
62 | )
63 | end
64 |
65 | nginx_site 'github_connector'
66 |
--------------------------------------------------------------------------------
/cookbook/recipes/ruby.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: ruby
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | # gawk is needed to install ruby 2.1.x but is not installed by RVM
21 | package 'gawk'
22 | # libgmp-dev is needed to install some native dependencies with Ruby 2.3.0
23 | package 'libgmp-dev'
24 |
25 | node.default['rvm']['user_installs'] = [{
26 | user: node['github_connector']['user'],
27 | home: "/home/#{node['github_connector']['user']}",
28 | upgrade: node['rvm']['version']
29 | }]
30 | include_recipe 'rvm::user'
31 |
32 | rvm_gemset node['github_connector']['ruby_gemset'] do
33 | user node['github_connector']['user']
34 | ruby_string node['github_connector']['ruby_version']
35 | end
36 |
--------------------------------------------------------------------------------
/cookbook/recipes/ssh.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: ssh
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | # Create custom wrapper scripts that allow deploying private repos
21 | repos = {}
22 | if node['github_connector']['ssh_databag'] && node['github_connector']['ssh_databag_item']
23 | repos['github_connector'] = {
24 | 'ssh_databag' => node['github_connector']['ssh_databag'],
25 | 'ssh_databag_item' => node['github_connector']['ssh_databag_item'],
26 | }
27 | end
28 |
29 | node['github_connector']['engines'].each do |engine, attrs|
30 | if attrs['ssh_databag'] && attrs['ssh_databag_item']
31 | repos[engine] = {
32 | 'ssh_databag' => attrs['ssh_databag'],
33 | 'ssh_databag_item' => attrs['ssh_databag_item'],
34 | }
35 | end
36 | end
37 |
38 |
39 | repos.each do |repo_name, attrs|
40 | ssh_data_bag = GithubConnector::Helpers.load_data_bag(
41 | attrs['ssh_databag'],
42 | attrs['ssh_databag_item']
43 | )
44 |
45 | if ssh_data_bag && ssh_data_bag['private_key']
46 | require 'net/ssh'
47 | private_key = OpenSSL::PKey::RSA.new(ssh_data_bag['private_key'])
48 | public_key = private_key.public_key
49 | ssh_dir = "/home/#{node['github_connector']['user']}/.ssh"
50 |
51 | directory ssh_dir do
52 | mode 0700
53 | owner node['github_connector']['user']
54 | group node['github_connector']['group']
55 | end
56 |
57 | file ::File.join(ssh_dir, "#{repo_name}_id_rsa") do
58 | content private_key.to_pem
59 | owner node['github_connector']['user']
60 | group node['github_connector']['group']
61 | mode 0600
62 | end
63 |
64 | file ::File.join(ssh_dir, "#{repo_name}_id_rsa.pub") do
65 | content "#{public_key.ssh_type} #{[public_key.to_blob].pack('m0')}\n"
66 | owner node['github_connector']['user']
67 | group node['github_connector']['group']
68 | mode 0644
69 | end
70 |
71 | file ::File.join(ssh_dir, "#{repo_name}_ssh_wrapper.sh") do
72 | content "#!/bin/sh -e\nexec ssh -i #{::File.join(ssh_dir, "#{repo_name}_id_rsa")} $@\n"
73 | owner node['github_connector']['user']
74 | group node['github_connector']['group']
75 | mode 0755
76 | end
77 | end
78 | end
79 |
80 | if node['github_connector']['github_host_key']
81 | ssh_known_hosts_entry 'github.com' do
82 | key node['github_connector']['github_host_key']
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/cookbook/recipes/upstart.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: upstart
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | directory "#{node['github_connector']['install_dir']}/tmp/sockets" do
21 | owner node['github_connector']['user']
22 | group node['github_connector']['group']
23 | mode 0755
24 | end
25 |
26 | template "/etc/init/github-connector-web.conf" do
27 | source 'upstart-github-connector-web.conf.erb'
28 | mode 0644
29 | owner 'root'
30 | group 'root'
31 | action :create
32 | variables(
33 | :home_path => "/home/#{node['github_connector']['user']}",
34 | :rvm_path => "/home/#{node['github_connector']['user']}/.rvm"
35 | )
36 | end
37 |
38 | template "/etc/init/github-connector-worker.conf" do
39 | source 'upstart-github-connector-worker.conf.erb'
40 | mode 0644
41 | owner 'root'
42 | group 'root'
43 | action :create
44 | variables(
45 | :home_path => "/home/#{node['github_connector']['user']}",
46 | :rvm_path => "/home/#{node['github_connector']['user']}/.rvm"
47 | )
48 | end
49 |
50 | service 'github-connector-web' do
51 | provider Chef::Provider::Service::Upstart
52 | supports :status => true, :restart => true, :reload => true
53 | action :start
54 | end
55 |
56 | service 'github-connector-worker' do
57 | provider Chef::Provider::Service::Upstart
58 | supports :status => true, :restart => true, :reload => false
59 | action :start
60 | end
61 |
--------------------------------------------------------------------------------
/cookbook/recipes/user.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Cookbook Name:: github_connector
3 | # Recipe:: user
4 | #
5 | # Copyright (C) 2014 Brandon Turner
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | group node['github_connector']['group']
21 |
22 | user node['github_connector']['user'] do
23 | gid node['github_connector']['group']
24 | shell '/bin/bash'
25 | home "/home/#{node['github_connector']['user']}"
26 | supports :manage_home => true
27 | end
28 |
--------------------------------------------------------------------------------
/cookbook/templates/default/database.yml.erb:
--------------------------------------------------------------------------------
1 | # THIS FILE IS MANAGED BY CHEF
2 | # Local modifications will be discarded.
3 |
4 | development: &default
5 | adapter: postgresql
6 | host: <%= node['github_connector']['db']['host'] %>
7 | port: <%= node['github_connector']['db']['port'] %>
8 | database: <%= node['github_connector']['db']['name'] %>
9 | user: <%= node['github_connector']['db']['user'] %>
10 | password: "<%= GithubConnector::Helpers.database_password(node) %>"
11 | pool: 25
12 | timeout: 5
13 |
14 | production:
15 | <<: *default
16 |
17 | # Warning: The database defined as "test" will be erased and
18 | # re-generated from your development database when you run "rake".
19 | # Do not set this db to the same as development or production.
20 | test:
21 | <<: *default
22 | database: github_connector_test
23 |
--------------------------------------------------------------------------------
/cookbook/templates/default/nginx-github-connector.conf.erb:
--------------------------------------------------------------------------------
1 | # THIS FILE IS MANAGED BY CHEF
2 | # Local modifications will be discarded.
3 |
4 | upstream github_connector_server {
5 | server unix://<%= @install_dir %>/tmp/sockets/puma.sock fail_timeout=0;
6 | }
7 |
8 | <% if @redirect_http %>
9 | server {
10 | listen <%= @listen_port %>;
11 | server_name <%= @host_name %> <%= @host_aliases.join(' ') %>;
12 | rewrite ^(.*) https://$host<%= ":#{@ssl_listen_port}" unless @ssl_listen_port == 443 %>$1 permanent;
13 | }
14 | <% end -%>
15 |
16 | server {
17 | <% if @ssl_enabled -%>
18 | listen <%= @ssl_listen_port %>;
19 | <% else -%>
20 | listen <%= @listen_port %>;
21 | <% end -%>
22 |
23 | server_name <%= @host_name %> <%= @host_aliases.join(' ') %>;
24 | root <%= @install_dir %>/public;
25 |
26 | keepalive_timeout 5s;
27 |
28 | <% if @ssl_enabled -%>
29 | ssl on;
30 | ssl_certificate /etc/ssl/certs/<%= @host_name %>.crt;
31 | ssl_certificate_key /etc/ssl/private/<%= @host_name %>.key;
32 |
33 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
34 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
35 | ssl_prefer_server_ciphers on;
36 |
37 | ssl_session_cache shared:SSL:10m;
38 | ssl_session_timeout 10m;
39 | <% end -%>
40 |
41 | try_files $uri/index.html $uri.html $uri @app;
42 |
43 | location @app {
44 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45 | proxy_set_header X-Forwarded-Proto $scheme;
46 | proxy_set_header Host $http_host;
47 | proxy_redirect off;
48 | proxy_pass http://github_connector_server;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/cookbook/templates/default/secrets.yml.erb:
--------------------------------------------------------------------------------
1 | # THIS FILE IS MANAGED BY CHEF
2 | # Local modifications will be discarded.
3 |
4 | # Be sure to restart your server when you modify this file.
5 | #
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 | #
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development: &default
14 | # Your secret key is used for verifying the integrity of signed cookies.
15 | # If you change this key, all old signed cookies will become invalid!
16 | secret_key_base: <%= GithubConnector::Helpers.secret_key_base(node) %>
17 |
18 | # The secret key used for encrypting sensitive database info.
19 | # If you change this key, all user OAuth tokens will become unreadable!
20 | database_key: <%= GithubConnector::Helpers.database_key(node) %>
21 |
22 | production:
23 | <<: *default
24 |
25 | test:
26 | <<: *default
27 |
--------------------------------------------------------------------------------
/cookbook/templates/default/upstart-github-connector-web.conf.erb:
--------------------------------------------------------------------------------
1 | # THIS FILE IS MANAGED BY CHEF
2 | # Local modifications will be discarded.
3 |
4 | description "GitHub Connector"
5 |
6 | start on runlevel [2]
7 | stop on runlevel [016]
8 |
9 | setuid <%= node['github_connector']['user'] %>
10 | chdir <%= node['github_connector']['install_dir'] %>
11 | exec <%= @rvm_path %>/bin/rvm <%= node['github_connector']['rvm_alias'] %> do puma -e production -b unix://<%= node['github_connector']['install_dir'] %>/tmp/sockets/puma.sock
12 | reload signal SIGUSR2
13 |
14 | respawn
15 |
--------------------------------------------------------------------------------
/cookbook/templates/default/upstart-github-connector-worker.conf.erb:
--------------------------------------------------------------------------------
1 | # THIS FILE IS MANAGED BY CHEF
2 | # Local modifications will be discarded.
3 |
4 | description "GitHub Connector Worker"
5 |
6 | start on runlevel [2]
7 | stop on runlevel [016]
8 |
9 | setuid <%= node['github_connector']['user'] %>
10 | chdir <%= node['github_connector']['install_dir'] %>
11 | exec env HOME=<%= @home_path %> RAILS_ENV=production <%= @rvm_path %>/bin/rvm <%= node['github_connector']['rvm_alias'] %> do <%= node['github_connector']['install_dir'] %>/bin/delayed_job run
12 |
13 | respawn
14 |
--------------------------------------------------------------------------------
/cookbook/test_data_bags/github_connector/secrets.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "secrets",
3 | "database_password": "badpass_db",
4 | "secret_key_base": "badkey_secret",
5 | "database_key": "badkey_db"
6 | }
7 |
--------------------------------------------------------------------------------
/cookbook/test_data_bags/github_connector/ssh.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "ssh",
3 | "private_key": "-----BEGIN RSA PRIVATE KEY-----\nTHIS_IS_A_FAKE_KEY\n-----END RSA PRIVATE KEY-----\n"
4 | }
5 |
--------------------------------------------------------------------------------
/cookbook/test_data_bags/github_connector/ssl_cert.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "ssl_cert",
3 | "cert": "-----BEGIN CERTIFICATE-----\nMIIEMzCCAxugAwIBAgIJAK/3uISa+d4LMA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNV\nBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQK\nEyFHaXRodWIgQWN0aXZlIERpcmVjdG9yeSBDb25uZWN0b3IxEjAQBgNVBAMTCWxv\nY2FsaG9zdDAeFw0xNDEwMDYwMjI3MjFaFw0xNTEwMDYwMjI3MjFaMG4xCzAJBgNV\nBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQK\nEyFHaXRodWIgQWN0aXZlIERpcmVjdG9yeSBDb25uZWN0b3IxEjAQBgNVBAMTCWxv\nY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQRWnGOJWvY\nQwXO7gBZ7BVH8WOh4Vdvsy+Smw7kjukf3ZeXJrCs2B6cQYLKkDbelX/C0YIGp3w2\nGTFCY1mgsmpZI+svzMrZ8hHi00sEUkXekRxWgT/Bbo2AirP/Fz/r0d8fYle5D+0n\nriBQP3il6ZkYUAlJ0tDlsqCv2oEXxb7bIH88/lwwRXkxidr0GBdTG7HWGGABzG+B\n775DVL5RYMkOLJa7sKP3PGVK1nKIubaVWiQ+jhdiaHOPk2VymWhw3r3tV9YExxYP\n3vwcPS/Of/5EGo/WJ/a5MW57uTb90rWamBDMLMjBJPUXKLiDpNAdW0GFKw+PdWGX\n8/wyqMNrDpUCAwEAAaOB0zCB0DAdBgNVHQ4EFgQURShade6A1YmMODPSNViGEoty\nnGwwgaAGA1UdIwSBmDCBlYAURShade6A1YmMODPSNViGEotynGyhcqRwMG4xCzAJ\nBgNVBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYD\nVQQKEyFHaXRodWIgQWN0aXZlIERpcmVjdG9yeSBDb25uZWN0b3IxEjAQBgNVBAMT\nCWxvY2FsaG9zdIIJAK/3uISa+d4LMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\nBQADggEBALEAKoLnLNcm4+oEMs97LV9FspaqJMbZSJbzQaPUN6DlQF9o09HgaEgU\nKI4GGuPUEt+wU9OQBDhWmbkJgyqPl3L6Mq8YazadPvIPwVEzTGcUWfeBDdrcmH5T\nNTimbVQgwliArbpXI/kgWJw4G7e3wn5ZptdFr3YscdwE1ki4vIYgXlIBKqXgW+Wh\nlF/T+s1cRPmoX24M1G5A3wgngLGshIVvv+Xr//keLvWpmS5z3uXUzBH3HeLwxrjO\nHzrjOnwi6OvSoBqMZjvLLv/yP9g0hh9DmiqKh0QmB90Vlp/bx5Dv1g5Fup3alNdp\nxjsyvSde1fKm3xSpFh+bXwiYvLHh8LY=\n-----END CERTIFICATE-----\n",
4 | "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAtBFacY4la9hDBc7uAFnsFUfxY6HhV2+zL5KbDuSO6R/dl5cm\nsKzYHpxBgsqQNt6Vf8LRgganfDYZMUJjWaCyalkj6y/MytnyEeLTSwRSRd6RHFaB\nP8FujYCKs/8XP+vR3x9iV7kP7SeuIFA/eKXpmRhQCUnS0OWyoK/agRfFvtsgfzz+\nXDBFeTGJ2vQYF1MbsdYYYAHMb4HvvkNUvlFgyQ4slruwo/c8ZUrWcoi5tpVaJD6O\nF2Joc4+TZXKZaHDeve1X1gTHFg/e/Bw9L85//kQaj9Yn9rkxbnu5Nv3StZqYEMws\nyMEk9RcouIOk0B1bQYUrD491YZfz/DKow2sOlQIDAQABAoIBABWj0D69Wnnvb36P\nM8MPC3QzRSs4FSCw59Pbxo6voQ0bK0JAhAHPg9mJ5cWWGma9sTG9c/gwXIhs5/In\njFEFIuvs8ogdIntuXc0QeVwWlNyYts+1Batnz6VpwUGIcn7YFEzANM1eDC/wCNkR\nS89wAPbJGTVEjfVU5XayK4xAEx+weh704U75pGJqI+N/Cd4aBIA8rLQ8S5cRLRRs\nx6HUvuysK1A92RTUS3Cvu8UR8lJYzmkgGGf72iZjtqx7IqKSeKLNNE+t1d4Vtigi\nhC8egVlRxFnG3rdrssUqT1+7i7T6RAOCiD8WK1U+fjSmhz6/n88heZ5XpKeUdkiQ\ny1xJsLECgYEA6GP1haXQvg/FIl7HBJw4BJgHUrlMEkwyfvFOF1UYacjHN7uNXV1B\nsSe+gm/MI2uiiNayjJChy7r3pSfckNdauI+0zxsysj6dSXp6B0/1YiKAAJXm3X5i\nx1F+mxWWaMUlyW7YEBO0I4j0bcS0knO6NffWs61OtqTw8OI4uqzYIHsCgYEAxlyU\nTUrBMPMoVNb2KlOVvXJwnEbbLVom3wBiMNg+RVqqzwIl1NB7Bz45n6dHuSCXhbOH\nA/5871aAXQHZq4zj2+aFyr+xAdypcU1Yez/Xio6Q0jkEd6QS/tKVvPFVXCQCGfvX\nYv4rvZLSI4MxG0paF2qp5DZlkm1Oios/4XEHyC8CgYEA1IeWY0PiQ+/oOiaznGPC\nV3EyQVV1XMaS58WHxY7tZNFaYH4GKvy+t2XBtUjJSRuG6d5wLF2ZmtjC4ygxb8WE\nEoZatY4KLzlUX37DWyylHbqvldmB6c9MRz0grHRxuh+TD0VwFEPw2w7FfB4JhmaQ\nRgsDMA+vjRoLwEEj4JVyk0ECgYEAnPcdk5wYDDgeLiR8XzoNQACTA9c+EUFJiSWw\njZ5QiGkayPyWGzVuZWjkCGZC50fXH0HVEWAMVQhKQ073hDzVAmoEbVALLcIDg1kF\nL2JxmX7/MptT4ajAL01MmFsQhP0pfI5A/mDLFBRenSNvdHz9lZIeJiy1a417nT5b\nqnXbBpkCgYBAKidtSdQ2a97HuO1Jctw4B1xOFlJwp36Bi3b3PN14l/hfnbVNg0KS\nols8DZZ7nh7hRjcds5w4174YUYkLfQZxfI5Wi48BoRdB+ruZR4DlGL+tWll28L/z\nQTUsuIdsNVvrv524BSW5lYv9ocpCjw23eGuLAkFnea7Thztg/Ez8jg==\n-----END RSA PRIVATE KEY-----\n"
5 | }
6 |
--------------------------------------------------------------------------------
/cortex.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | info:
3 | title: Github Connector
4 | description: The GitHub Active Directory Connector allows managing GitHub organizations
5 | with Active Directory.
6 | x-cortex-git:
7 | github:
8 | alias: r7org
9 | repository: rapid7/github-connector
10 | x-cortex-tag: github-connector
11 | x-cortex-type: service
12 | x-cortex-domain-parents:
13 | - tag: pd-services
14 | openapi: 3.0.1
15 | servers:
16 | - url: "/"
17 |
--------------------------------------------------------------------------------
/db/migrate/20140619160007_devise_create_users.rb:
--------------------------------------------------------------------------------
1 | class DeviseCreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table(:users) do |t|
4 | ## LDAP authenticatable
5 | t.string :username, null: false, default: ""
6 | t.string :email
7 | t.string :name
8 |
9 | ## Rememberable
10 | t.datetime :remember_created_at
11 |
12 | ## Trackable
13 | t.integer :sign_in_count, default: 0, null: false
14 | t.datetime :current_sign_in_at
15 | t.datetime :last_sign_in_at
16 | t.string :current_sign_in_ip
17 | t.string :last_sign_in_ip
18 |
19 | ## Confirmable
20 | # t.string :confirmation_token
21 | # t.datetime :confirmed_at
22 | # t.datetime :confirmation_sent_at
23 | # t.string :unconfirmed_email # Only if using reconfirmable
24 |
25 | ## Lockable
26 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
27 | # t.string :unlock_token # Only if unlock strategy is :email or :both
28 | # t.datetime :locked_at
29 |
30 |
31 | t.timestamps
32 | end
33 |
34 | add_index :users, :username, unique: true
35 | # add_index :users, :confirmation_token, unique: true
36 | # add_index :users, :unlock_token, unique: true
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/db/migrate/20140624041139_add_github_attrs_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddGithubAttrsToUser < ActiveRecord::Migration
2 | def change
3 | add_column :users, :encrypted_github_token, :string
4 | add_column :users, :github_login, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20140626181353_create_settings.rb:
--------------------------------------------------------------------------------
1 | class CreateSettings < ActiveRecord::Migration
2 | def change
3 | create_table :settings do |t|
4 | t.string :key
5 | t.string :value
6 |
7 | t.timestamps
8 | end
9 | add_index :settings, :key, unique: true
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20140708224056_create_emails.rb:
--------------------------------------------------------------------------------
1 | class CreateEmails < ActiveRecord::Migration
2 | def change
3 | create_table :emails do |t|
4 | t.references :user, index: true
5 | t.string :address
6 | t.string :source
7 |
8 | t.timestamps
9 | end
10 | add_index :emails, :source
11 |
12 | reversible do |dir|
13 | dir.up do
14 | execute "INSERT INTO emails (user_id, address, source, created_at, updated_at) SELECT id, email, 'ldap', NOW(), NOW() FROM users WHERE email IS NOT NULL"
15 | remove_column :users, :email
16 | end
17 | dir.down do
18 | add_column :users, :email, :string
19 | execute "UPDATE users AS u SET email=emails.address, updated_at=NOW() FROM users INNER JOIN emails ON users.id=emails.user_id"
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/db/migrate/20140709045852_add_last_sync_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddLastSyncToUser < ActiveRecord::Migration
2 | def change
3 | add_column :users, :last_ldap_sync, :datetime
4 | add_column :users, :last_github_sync, :datetime
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20140709191104_add_state_attrs_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddStateAttrsToUser < ActiveRecord::Migration
2 | def change
3 | add_column :users, :state, :string, null: false, default: :unknown
4 | add_column :users, :ldap_account_control, :integer
5 | add_column :users, :github_mfa, :boolean
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20140714210644_add_sync_errors_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddSyncErrorsToUser < ActiveRecord::Migration
2 | def change
3 | add_column :users, :github_sync_error, :string
4 | add_column :users, :github_sync_error_at, :datetime
5 | add_column :users, :ldap_sync_error, :string
6 | add_column :users, :ldap_sync_error_at, :datetime
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20140722192112_add_github_teams.rb:
--------------------------------------------------------------------------------
1 | class AddGithubTeams < ActiveRecord::Migration
2 | def change
3 | create_table(:teams) do |t|
4 | t.string :slug
5 | t.string :organization
6 | t.string :name
7 | t.timestamps
8 | end
9 |
10 | create_table :user_teams, id: false do |t|
11 | t.belongs_to :user
12 | t.belongs_to :team
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20140726214806_move_state_to_github_user.rb:
--------------------------------------------------------------------------------
1 | class MoveStateToGithubUser < ActiveRecord::Migration
2 | def change
3 | add_column :github_users, :state, :string, null: false, default: :unknown
4 | remove_column :users, :state, :string, null: false, default: :unknown
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20140811194159_add_github_urls.rb:
--------------------------------------------------------------------------------
1 | class AddGithubUrls < ActiveRecord::Migration
2 | def change
3 | add_column :github_users, :avatar_url, :string
4 | add_column :github_users, :html_url, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20140818012538_add_admin_flag_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddAdminFlagToUser < ActiveRecord::Migration
2 | def change
3 | add_column :users, :admin, :bool
4 |
5 | reversible do |dir|
6 | dir.up do
7 | execute <<-SQL
8 | UPDATE users SET admin='t'
9 | WHERE users.id IN (SELECT id FROM users ORDER BY created_at LIMIT 1)
10 | SQL
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20140915164525_convert_settings_value_to_text.rb:
--------------------------------------------------------------------------------
1 | class ConvertSettingsValueToText < ActiveRecord::Migration
2 | def change
3 | change_column :settings, :value, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20140917184213_create_delayed_jobs.rb:
--------------------------------------------------------------------------------
1 | class CreateDelayedJobs < ActiveRecord::Migration
2 | def self.up
3 | create_table :delayed_jobs, :force => true do |table|
4 | table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue
5 | table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually.
6 | table.text :handler, :null => false # YAML-encoded string of the object that will do work
7 | table.text :last_error # reason for last failure (See Note below)
8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9 | table.datetime :locked_at # Set when a client is working on this object
10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11 | table.string :locked_by # Who is working on this object (if locked)
12 | table.string :queue # The name of the queue this job is in
13 | table.timestamps
14 | end
15 |
16 | add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
17 | end
18 |
19 | def self.down
20 | drop_table :delayed_jobs
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20140917184236_add_connect_github_user_statuses.rb:
--------------------------------------------------------------------------------
1 | class AddConnectGithubUserStatuses < ActiveRecord::Migration
2 | def change
3 | create_table(:connect_github_user_statuses) do |t|
4 | t.belongs_to :user
5 | t.belongs_to :github_user
6 | t.string :oauth_code
7 | t.string :status
8 | t.string :step
9 | t.text :error_message
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20140920200517_add_remember_token_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddRememberTokenToUser < ActiveRecord::Migration
2 | def change
3 | add_column :users, :remember_token, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20141018212156_add_github_user_disabled_teams.rb:
--------------------------------------------------------------------------------
1 | class AddGithubUserDisabledTeams < ActiveRecord::Migration
2 | def change
3 | create_table :github_user_disabled_teams, id: false do |t|
4 | t.belongs_to :github_user
5 | t.belongs_to :github_team
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20160215025445_add_github_organization_memberships.rb:
--------------------------------------------------------------------------------
1 | class AddGithubOrganizationMemberships < ActiveRecord::Migration
2 | def change
3 | create_table :github_organization_memberships do |t|
4 | t.references :github_user, index: true, null: false
5 | t.string :organization, null: false
6 | t.string :role
7 | t.string :state
8 |
9 | t.timestamps null: false
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20210311145806_add_user_department.rb:
--------------------------------------------------------------------------------
1 | class AddUserDepartment < ActiveRecord::Migration
2 | def change
3 | add_column :users, :department, :string
4 | end
5 | end
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
--------------------------------------------------------------------------------
/ldap/README.md:
--------------------------------------------------------------------------------
1 | Development LDAP Server
2 | =======================
3 |
4 | The code in this directory uses OpenLDAP to emulate the Active
5 | Directory records needed for this application to work. This is
6 | helpful in development and testing if you do not want to connect
7 | to a real Active Directory server.
8 |
9 | ## Install prerequisites
10 |
11 | ### Ubuntu
12 |
13 | Install OpenLDAP's slapd:
14 |
15 | sudo apt-get install slapd ldap-utils
16 |
17 | You may also need to put apparmor into complain mode:
18 |
19 | sudo apt-get install apparmor-utils
20 | sudo aa-complain /usr/sbin/slapd
21 |
22 | ### OSX
23 |
24 | OpenLDAP is installed on OSX by default. There is nothing else
25 | you need to do.
26 |
27 | ## Run test server
28 |
29 | To run the server:
30 |
31 | ./run-server
32 |
33 | ## Accounts
34 |
35 | Several accounts are available:
36 |
37 | * hsimpson - Normal account
38 | * msimpson - Locked account
39 | * bsimpson - Disabled account
40 | * lsimpson - Password expired
41 |
42 | All accounts use password 123456.
43 |
--------------------------------------------------------------------------------
/ldap/base.ldif:
--------------------------------------------------------------------------------
1 |
2 | dn: dc=example,dc=com
3 | objectClass: dcObject
4 | objectClass: organizationalUnit
5 | dc: example
6 | ou: example
7 |
8 | # Normal account
9 | dn: cn=Homer Simpson,dc=example,dc=com
10 | objectclass: top
11 | objectClass: person
12 | objectClass: organizationalPerson
13 | objectClass: user
14 | displayName: Homer Simpson
15 | name: Homer Simpson
16 | givenName: Homer
17 | sn: Simpson
18 | mail: Homer_Simpson@example.com
19 | userPrincipalName: hsimpson@example.com
20 | userAccountControl: 512
21 | sAMAccountName: hsimpson
22 | # userPassword: 123456
23 | userPassword: {SSHA}1j5ho2mHI6fHgwQOjBk9aRHF47FzYWx0
24 |
25 | # Locked account
26 | dn: cn=Marge Simpson,dc=example,dc=com
27 | objectclass: top
28 | objectClass: person
29 | objectClass: organizationalPerson
30 | objectClass: user
31 | displayName: Homer Simpson
32 | name: Marge Simpson
33 | givenName: Marge
34 | sn: Simpson
35 | mail: Marge_Simpson@example.com
36 | userPrincipalName: msimpson@example.com
37 | userAccountControl: 528
38 | sAMAccountName: msimpson
39 | # userPassword: 123456
40 | userPassword: {SSHA}1j5ho2mHI6fHgwQOjBk9aRHF47FzYWx0
41 |
42 | # Disabled account
43 | dn: cn=Bart Simpson,dc=example,dc=com
44 | objectclass: top
45 | objectClass: person
46 | objectClass: organizationalPerson
47 | objectClass: user
48 | displayName: Homer Simpson
49 | name: Bart Simpson
50 | givenName: Bart
51 | sn: Simpson
52 | mail: Bart_Simpson@example.com
53 | userPrincipalName: bsimpson@example.com
54 | userAccountControl: 514
55 | sAMAccountName: bsimpson
56 | # userPassword: 123456
57 | userPassword: {SSHA}1j5ho2mHI6fHgwQOjBk9aRHF47FzYWx0
58 |
59 | # Password expired
60 | dn: cn=Lisa Simpson,dc=example,dc=com
61 | objectclass: top
62 | objectClass: person
63 | objectClass: organizationalPerson
64 | objectClass: user
65 | displayName: Homer Simpson
66 | name: Lisa Simpson
67 | givenName: Lisa
68 | sn: Simpson
69 | mail: Lisa_Simpson@example.com
70 | userPrincipalName: lsimpson@example.com
71 | userAccountControl: 8389120
72 | sAMAccountName: lsimpson
73 | # userPassword: 123456
74 | userPassword: {SSHA}1j5ho2mHI6fHgwQOjBk9aRHF47FzYWx0
75 |
--------------------------------------------------------------------------------
/ldap/clear.ldif:
--------------------------------------------------------------------------------
1 | dn: cn=Lisa Simpson,dc=example,dc=com
2 | changetype: delete
3 |
4 | dn: cn=Bart Simpson,dc=example,dc=com
5 | changetype: delete
6 |
7 | dn: cn=Marge Simpson,dc=example,dc=com
8 | changetype: delete
9 |
10 | dn: cn=Homer Simpson,dc=example,dc=com
11 | changetype: delete
12 |
13 | dn: dc=example,dc=com
14 | changetype: delete
15 |
--------------------------------------------------------------------------------
/ldap/local.schema:
--------------------------------------------------------------------------------
1 |
2 | attributetype ( 1.2.840.113556.1.4.656
3 | NAME 'userPrincipalName'
4 | EQUALITY caseIgnoreMatch
5 | SUBSTR caseIgnoreSubstringsMatch
6 | SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
7 | SINGLE-VALUE )
8 |
9 | attributetype ( 1.2.840.113556.1.4.221
10 | NAME 'sAMAccountName'
11 | EQUALITY caseIgnoreMatch
12 | SUBSTR caseIgnoreSubstringsMatch
13 | SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
14 | SINGLE-VALUE )
15 |
16 | attributetype ( 1.2.840.113556.1.4.8
17 | NAME 'userAccountControl'
18 | EQUALITY integerMatch
19 | SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
20 | SINGLE-VALUE )
21 |
22 | objectclass ( 1.2.840.113556.1.5.9
23 | NAME 'user'
24 | SUP organizationalPerson
25 | STRUCTURAL
26 | MUST ( sAMAccountName $ userAccountControl $ userPrincipalName )
27 | MAY ( displayName $ givenName $ mail $ name ) )
28 |
--------------------------------------------------------------------------------
/ldap/run-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'erb'
4 | require 'fileutils'
5 | require 'open3'
6 |
7 | FileUtils.chdir(__dir__)
8 |
9 | ## For OSX:
10 | ENV['PATH'] = "#{ENV['PATH']}:/usr/libexec"
11 | FileUtils.mkdir_p(File.join(__dir__, 'openldap-data', 'run'))
12 |
13 | template = File.read('slapd-test.conf.erb')
14 | normal_out = 'slapd-test.conf'
15 |
16 | File.open(normal_out, 'w') do |f|
17 | f.write ERB.new(template).result(binding)
18 | end
19 |
20 | cmd = "slapd -d 32768 -f #{normal_out} -h ldap://localhost:3268"
21 |
22 | started = false
23 | @slap_pid = nil
24 | slap_thread = Thread.new do
25 | Thread.current.abort_on_exception = true
26 | Open3.popen2(cmd) do |stdin, stdout, wait_thr|
27 | @slap_pid = wait_thr.pid
28 | stdin.close
29 | begin
30 | while data = stdout.readpartial(1024)
31 | print data
32 | end
33 | rescue EOFError
34 | # Ignore EOF
35 | end
36 | stdout.close
37 | exit_status = wait_thr.value
38 | end
39 | end
40 |
41 | def kill_slapd
42 | if @slap_pid
43 | Process.kill('TERM', @slap_pid) rescue nil
44 | end
45 | end
46 |
47 | Signal.trap('INT') { kill_slapd }
48 | Signal.trap('TERM') { kill_slapd }
49 |
50 | begin
51 | # TODO: Better test for slapd started
52 | sleep 0.5
53 |
54 | if slap_thread.alive?
55 | ldap_connect_string = "-x -H ldap://localhost:3268 -D 'cn=admin,dc=example,dc=com' -w secret"
56 | system("ldapmodify #{ldap_connect_string} -f clear.ldif")
57 | system("ldapadd #{ldap_connect_string} -f base.ldif")
58 | end
59 |
60 | slap_thread.join
61 | ensure
62 | kill_slapd
63 | end
64 |
--------------------------------------------------------------------------------
/ldap/slapd-test.conf.erb:
--------------------------------------------------------------------------------
1 | #
2 | # See slapd.conf(5) for details on configuration options.
3 | # This file should NOT be world readable.
4 | #
5 | <% ldapdir = RUBY_PLATFORM.match(/linux/) ? 'ldap' : 'openldap' %>
6 | include /etc/<%= ldapdir %>/schema/core.schema
7 | include /etc/<%= ldapdir %>/schema/cosine.schema
8 | include /etc/<%= ldapdir %>/schema/inetorgperson.schema
9 | #include /etc/<%= ldapdir %>/schema/nis.schema
10 | #include /etc/<%= ldapdir %>/schema/microsoft.std.schema
11 | #include /etc/<%= ldapdir %>/schema/microsoft.schema
12 |
13 | ## Local definitions
14 | include <%= File.expand_path('local.schema', @conf_root) %>
15 |
16 | # Allow LDAPv2 client connections. This is NOT the default.
17 | allow bind_v2
18 |
19 | # Do not enable referrals until AFTER you have a working directory
20 | # service AND an understanding of referrals.
21 | #referral ldap://root.openldap.org
22 |
23 | pidfile <%= File.expand_path('openldap-data/run/slapd.pid', @conf_root) %>
24 | argsfile <%= File.expand_path('openldap-data/run/slapd.args', @conf_root) %>
25 |
26 | # Load dynamic backend modules:
27 | modulepath /usr/lib/openldap
28 |
29 | access to *
30 | by self write
31 | by * read
32 | by anonymous auth
33 |
34 | #######################################################################
35 | # ldbm and/or bdb database definitions
36 | #######################################################################
37 |
38 | database ldif
39 |
40 | suffix "dc=example,dc=com"
41 | directory openldap-data
42 | rootdn "cn=admin,dc=example,dc=com"
43 | ## rootpw = secret
44 | rootpw {SSHA}fFjKcZb4cfOAcwSjJer8nCGOEVRUnwCC
45 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/base_executor.rb:
--------------------------------------------------------------------------------
1 | class BaseExecutor
2 |
3 | # A list of errors that occurred while running this synchronizer
4 | # @return [Array]
5 | attr_reader :errors
6 |
7 | # The number of threads to use when running.
8 | # @return [Fixnum]
9 | attr_accessor :thread_count
10 |
11 | # Runs the executor.
12 | #
13 | # @return [BaseExecutor] instance of the executor that ran the task
14 | def self.run!
15 | new.tap { |instance| instance.run! }
16 | end
17 |
18 | def initialize
19 | @thread_count = [5, ActiveRecord::Base.connection_pool.size - 1].min
20 | @semaphore = Mutex.new
21 | @errors = []
22 | end
23 |
24 | private
25 |
26 | # Obtains a lock, runs the block, and releases the lock when the block completes.
27 | #
28 | # @see `Mutex#synchronize`
29 | def synchronize(&block)
30 | @semaphore.synchronize(&block)
31 | end
32 |
33 | # Runs the given block for each object in parallel. Up to
34 | # {#thread_count} threads are used to run the block. This waits
35 | # for all threads to complete before returning.
36 | #
37 | # @param objs [Enumerable] an array of objects
38 | # @yieldparam obj [Object] a single object from the array of objects
39 | # @return [void]
40 | def thread_for_each(objs)
41 | ary = objs.to_a
42 | ary = ary.dup if ary === objs
43 |
44 | threads = []
45 | [ary.size, thread_count].min.times do
46 | threads << Thread.new do |thread|
47 | ActiveRecord::Base.connection_pool.with_connection do
48 | while (obj = synchronize { ary.shift })
49 | yield obj
50 | end
51 | end
52 | end
53 | end
54 | threads.each { |t| t.join }
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/github_connector/navbar.rb:
--------------------------------------------------------------------------------
1 | module GithubConnector
2 | class Navbar
3 | include Rails.application.routes.url_helpers
4 |
5 | def sections
6 | {
7 | connect: {
8 | title: 'Add Account',
9 | url: connect_path,
10 | },
11 | }
12 | end
13 |
14 | def admin_sections
15 | {
16 | users: {
17 | title: 'Users',
18 | url: users_path,
19 | },
20 | github_users: {
21 | title: 'GitHub Users',
22 | url: github_users_path,
23 | },
24 | }
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/ldap_synchronizer.rb:
--------------------------------------------------------------------------------
1 | class LdapSynchronizer < BaseExecutor
2 |
3 | # A hash of statistics from the most recent run. Includes:
4 | # * users_time - User sync execution time
5 | # * users_synced
6 | # * users_errors
7 | #
8 | # @return [Hash]
9 | attr_reader :stats
10 |
11 | # @return [Enumerable] a list of users to synchronize
12 | attr_reader :users
13 |
14 | # @param users [Enumerable] a list of users to synchronize
15 | def initialize(users=User.all)
16 | super()
17 | @stats = {}
18 | @users = users
19 | end
20 |
21 | # Synchronizes all Active Directory users.
22 | #
23 | # @return [Bolean]
24 | def sync_users
25 | start = Time.now
26 | stats[:users_synced] = 0
27 | stats[:user_errors] = 0
28 | threads = []
29 |
30 | thread_for_each(users) do |user|
31 | begin
32 | user.sync_from_ldap!
33 | synchronize do
34 | if user.ldap_sync_error
35 | stats[:user_errors] += 1
36 | @errors << "Error synchronizing #{user.username}: #{user.ldap_sync_error}"
37 | else
38 | stats[:users_synced] += 1
39 | end
40 | end
41 | rescue => e
42 | synchronize do
43 | stats[:user_errors] += 1
44 | @errors << e
45 | Rails.logger.error "Error processing user #{user.username}: #{e.message}"
46 | end
47 | end
48 | end
49 |
50 | stats[:user_errors] == 0
51 | rescue => e
52 | stats[:user_errors] += 1
53 | @errors << e
54 | Rails.logger.error "Error synchronizing users: #{e.message}"
55 | false
56 | ensure
57 | stats[:users_time] = Time.now.to_f - start.to_f
58 | end
59 |
60 | # Synchronizes Active Directory users with our local database.
61 | # Synchronization is run in threads according to {#thread_count}.
62 | #
63 | # @return [Boolean] `true` if synchronizer executed successfully, `false` otherwise.
64 | def run!
65 | @errors = []
66 | @stats = {}
67 |
68 | Rails.application.settings.with_disconnected do |settings|
69 | settings.reload
70 |
71 | sync_users
72 | end
73 |
74 | @errors.empty?
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/rules.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | # @return [Array] an array of enabled {Rules::Base} classes
3 | def self.enabled_rules
4 | @all_rules ||= begin
5 | Dir[File.join(File.dirname(__FILE__), 'rules', '*.rb')].map do |file|
6 | filename = File.basename(file, '.rb')
7 | next if filename == 'base'
8 | Rules.const_get(filename.classify, false) rescue nil
9 | end.compact
10 | end
11 | @all_rules.select { |rule| rule.enabled? }
12 | end
13 |
14 | # @param user [GithubUser]
15 | # @return [Rules::Iterator] an array of {Rules::Base}s for the given user
16 | def self.for_github_user(user)
17 | rules = enabled_rules.map { |klass| klass.new(user) }
18 | Iterator.new(rules)
19 | end
20 |
21 | ##
22 | # An `Enumerable` wrapper around rules. It allows filtering
23 | # and provides summary methods. Assign a `Proc` to
24 | # {Iterator#selectors} to filter rules.
25 | class Iterator
26 | include ::Enumerable
27 |
28 | # @return [Array] an array of rules
29 | attr_reader :rules
30 |
31 | # @return [Proc] callbacks to filter rules
32 | attr_accessor :selectors
33 |
34 | def initialize(rules)
35 | @rules = rules
36 | @selectors = []
37 | end
38 |
39 | def initialize_copy(other)
40 | super
41 | @selectors = other.selectors.dup
42 | end
43 |
44 | # Calls the given block once for each element in `self`, passing that
45 | # element as a parameter. Elements are filtered by {#selectors} if
46 | # set.
47 | #
48 | # @yieldparam element [Rules:Base] a rule
49 | # @return [void]
50 | def each(&block)
51 | rules.each do |rule|
52 | next unless selectors.all? { |selector| selector.call(rule) }
53 | block.call(rule)
54 | end
55 | end
56 |
57 | # Returns `true` if `self` contains no elements
58 | # @return [Boolean]
59 | def empty?
60 | !any? { true }
61 | end
62 |
63 | # Includes only failing rules
64 | #
65 | # @return [Iterator] self
66 | def failing
67 | self.selectors << lambda { |rule| !rule.valid? }
68 | self
69 | end
70 |
71 | # Includes only rules required for external access
72 | #
73 | # @return [Iterator] self
74 | def external
75 | self.selectors << lambda { |rule| rule.required_for_external? }
76 | self
77 | end
78 |
79 | # Includes only passing rules
80 | #
81 | # @return [Iterator] self
82 | def passing
83 | self.selectors << lambda { |rule| rule.valid? }
84 | self
85 | end
86 |
87 | # Returns the result of all rules in `self`
88 | # @return [Boolean] `true` if all rules are valid, `false` otherwise
89 | def result
90 | all?(&:result)
91 | end
92 |
93 | # Returns `true` if all rules in `self` are valid
94 | # @return [Boolean]
95 | def valid?
96 | !!result
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/lib/rules/active_ldap.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | ##
3 | # Tests that the Active Directory account is active. The userAccountControl
4 | # LDAP attribute is used to check for disabled users (disabled flag: 0x0002).
5 | class ActiveLdap < Base
6 |
7 | # A descriptive error message to display when this rule
8 | # fails.
9 | #
10 | # @return [String]
11 | def error_msg
12 | return nil if result
13 |
14 | if user && has_flag?(User::AccountControl::ACCOUNT_DISABLED)
15 | "Active Directory account is disabled"
16 | #elsif user && has_flag?(User::AccountControl::PASSWORD_EXPIRED)
17 | # "Active Directory password is expired"
18 | else
19 | "Active Directory account does not meet criteria"
20 | end
21 | end
22 |
23 | # Should this rule notify the user when it is not valid?
24 | # @return [Boolean]
25 | def notify?
26 | false
27 | end
28 |
29 | # This rule is required for external users.
30 | #
31 | # @return [Boolean] false
32 | def required_for_external?
33 | false
34 | end
35 |
36 | # The result of applying this rule to the {User}.
37 | # @return [Boolean] `true` if the rule passes, false otherwise
38 | def result
39 | return false unless user
40 | return false if has_flag?(User::AccountControl::ACCOUNT_DISABLED)
41 | #return false if has_flag?(User::AccountControl::PASSWORD_EXPIRED)
42 | true
43 | end
44 |
45 | private
46 |
47 | def has_flag?(flag)
48 | user.ldap_account_control & flag == flag
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/rules/base.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | class Base
3 | # @return [GithubUser]
4 | attr_reader :github_user
5 |
6 | # Returns true if this rule is enabled.
7 | #
8 | # @return [Boolean]
9 | def self.enabled?
10 | true
11 | end
12 |
13 | def initialize(github_user)
14 | @github_user = github_user
15 | end
16 |
17 | # A descriptive error message to display when this rule
18 | # fails.
19 | #
20 | # @return [String]
21 | def error_msg
22 | name
23 | end
24 |
25 | # A name for this rule.
26 | #
27 | # @return [String]
28 | def name
29 | self.class.name.demodulize.underscore
30 | end
31 |
32 | # Should this rule notify the user when it is not valid?
33 | # @return [Boolean]
34 | def notify?
35 | true
36 | end
37 |
38 | # This rule is required for external users.
39 | #
40 | # @return [Boolean]
41 | def required_for_external?
42 | true
43 | end
44 |
45 | # The result of applying this rule to the {GithubUser}.
46 | # @return [Boolean] `true` if the rule passes, false otherwise
47 | def result
48 | raise NotImplementedError, "You must implement #{self.class.name}#result"
49 | end
50 |
51 | # Application settings
52 | # @return [GithubConnector::Settings]
53 | def settings
54 | self.class.settings
55 | end
56 |
57 | # Application settings
58 | # @return [GithubConnector::Settings]
59 | def self.settings
60 | Rails.application.settings
61 | end
62 |
63 | # The {User} associated with the {GithubUser}
64 | # @return [User]
65 | def user
66 | github_user.user
67 | end
68 |
69 | # Returns `true` if the result of the rule is `true`
70 | # @return [Boolean]
71 | def valid?
72 | !!result
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/rules/email.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | ##
3 | # Tests that all GitHub email addresses match the
4 | # {GithubConnector::Settings#rule_email_regex} setting. If
5 | # no `rule_email_regex` setting exists, this rule always
6 | # returns `true`.
7 | class Email < Base
8 |
9 | # Returns true if this rule is enabled.
10 | #
11 | # @return [Boolean]
12 | def self.enabled?
13 | !!settings.rule_email_regex
14 | end
15 |
16 | # A descriptive error message to display when this rule
17 | # fails.
18 | #
19 | # @return [String]
20 | def error_msg
21 | return nil if result
22 |
23 | bad_emails = email_addresses.reject { |email| regex.match(email) }
24 |
25 | "#{bad_emails.count == 1 ? 'Email does' : 'Emails do'} not meet criteria: #{bad_emails.join(', ')}"
26 | end
27 |
28 | # This rule is required for external users.
29 | #
30 | # @return [Boolean] false
31 | def required_for_external?
32 | false
33 | end
34 |
35 | # The result of applying this rule to the {GithubUser}.
36 | # @return [Boolean] `true` if the rule passes, false otherwise
37 | def result
38 | email_addresses.all? { |email| regex.match(email) }
39 | end
40 |
41 | private
42 |
43 | def email_addresses
44 | github_user.emails.map { |email| email.address.downcase }
45 | end
46 |
47 | def regex
48 | @regex ||= Regexp.new(settings.rule_email_regex)
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/rules/github_mfa.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | ##
3 | # Tests that the {GithubUser} has GitHub multi-factor authentication
4 | # enabled.
5 | class GithubMfa < Base
6 |
7 | # A descriptive error message to display when this rule
8 | # fails.
9 | #
10 | # @return [String]
11 | def error_msg
12 | return nil if result
13 |
14 | "Two factor authentication is disabled"
15 | end
16 |
17 | # The result of applying this rule to the {GithubUser}.
18 | # @return [Boolean] `true` if the rule passes, false otherwise
19 | def result
20 | !!github_user.mfa
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/rules/github_oauth.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | ##
3 | # Tests that a {GithubUser} has valid GitHub OAuth access. This is
4 | # evaluated by looking at the {GithubUser#sync_error} field for
5 | # `notoken` or `unauthorized` errors.
6 | class GithubOauth < Base
7 |
8 | # A descriptive error message to display when this rule
9 | # fails.
10 | #
11 | # @return [String]
12 | def error_msg
13 | return nil if result
14 |
15 | if github_user.token
16 | "Invalid OAuth token"
17 | else
18 | "Missing OAuth token"
19 | end
20 | end
21 |
22 | # This rule is required for external users.
23 | #
24 | # @return [Boolean] false
25 | def required_for_external?
26 | false
27 | end
28 |
29 | # The result of applying this rule to the {GithubUser}.
30 | # @return [Boolean] `true` if the rule passes, false otherwise
31 | def result
32 | return false unless github_user.token
33 | return false if %w(notoken unauthorized).include?(github_user.sync_error)
34 | true
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/rules/last_github_sync.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | ##
3 | # Tests that a {GithubUser} has synced with GitHub
4 | # within a certain amount of time specified by the
5 | # {GithubConnector::Settings#rule_max_sync_age} setting. If no
6 | # `rule_max_sync_age` setting exists, this rule always returns `true`.
7 | class LastGithubSync < Base
8 |
9 | # Returns true if this rule is enabled.
10 | #
11 | # @return [Boolean]
12 | def self.enabled?
13 | !!settings.rule_max_sync_age
14 | end
15 |
16 | # A descriptive error message to display when this rule
17 | # fails.
18 | #
19 | # @return [String]
20 | def error_msg
21 | return nil if result
22 |
23 | if !github_user.last_sync_at
24 | "GitHub has never been synchronized"
25 | else
26 | "Last GitHub synchronization is too old"
27 | end
28 | end
29 |
30 | # The result of applying this rule to the {GithubUser}.
31 | # @return [Boolean] `true` if the rule passes, false otherwise
32 | def result
33 | return false unless github_user.last_sync_at
34 |
35 | min_sync_time = Time.now - settings.rule_max_sync_age
36 | return false unless github_user.last_sync_at > min_sync_time
37 |
38 | true
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/rules/last_ldap_sync.rb:
--------------------------------------------------------------------------------
1 | module Rules
2 | ##
3 | # Tests that a {GithubUser} has synced with Active Directory
4 | # within a certain amount of time specified by the
5 | # {GithubConnector::Settings#rule_max_sync_age} setting. If no
6 | # `rule_max_sync_age` setting exists, this rule always returns `true`.
7 | class LastLdapSync < Base
8 |
9 | # Returns true if this rule is enabled.
10 | #
11 | # @return [Boolean]
12 | def self.enabled?
13 | !!settings.rule_max_sync_age
14 | end
15 |
16 | # A descriptive error message to display when this rule
17 | # fails.
18 | #
19 | # @return [String]
20 | def error_msg
21 | return nil if result
22 |
23 | if !user
24 | "No active directory user"
25 | elsif !user.last_ldap_sync
26 | "Active Directory has never been synchronized"
27 | else
28 | "Last Active Directory synchornization is too old"
29 | end
30 | end
31 |
32 | # This rule is required for external users.
33 | #
34 | # @return [Boolean] false
35 | def required_for_external?
36 | false
37 | end
38 |
39 | # The result of applying this rule to the {GithubUser}.
40 | # @return [Boolean] `true` if the rule passes, false otherwise
41 | def result
42 | return false unless user
43 | return false unless user.last_ldap_sync
44 |
45 | min_sync_time = Time.now - settings.rule_max_sync_age
46 | return false unless user.last_ldap_sync > min_sync_time
47 |
48 | true
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/settings/definition.rb:
--------------------------------------------------------------------------------
1 | module Settings
2 | ##
3 | # Defines a setting used in {Settings::Base}.
4 | class Definition
5 | # The setting name
6 | # @return [Symbol]
7 | attr_accessor :key
8 |
9 | # The setting type
10 | # @return [Symbol] one of `:string`, `:integer`, `:float`, `:boolean`, `:datetime`, `:array`, `:hash`
11 | attr_accessor :type
12 |
13 | # Whether the value should be encrypted in the database
14 | # @return [Boolean] true if the value should be encrypted, false otherwise
15 | attr_accessor :encrypt
16 |
17 | def initialize(key, opts)
18 | self.key = key.to_sym
19 | self.type = :string
20 | self.encrypt = false
21 | opts.each do |opt, val|
22 | send("#{opt}=", val) if respond_to?("#{opt}=")
23 | end
24 | end
25 |
26 | # Casts the given value for persistence in the database.
27 | #
28 | # @param [Object] val
29 | # @return [String] a string for persisting in the database
30 | def db_cast(val)
31 | return nil if val.nil?
32 |
33 | val = case type
34 | when :boolean then val ? 'true' : 'false'
35 | when :array, :hash then val ? val.to_json : nil
36 | else val.to_s
37 | end
38 |
39 | val
40 | end
41 |
42 | # Should the setting be encrypted when persisting?
43 | # @return [Boolean] true if the value should be encrypted, false otherwise
44 | def encrypt?
45 | !!@encrypt
46 | end
47 |
48 | # Casts the given value according to the `type` setting option.
49 | #
50 | # @param [Object] val
51 | # @return [Object] the value, cast according to the `type` option
52 | def type_cast(val)
53 | return nil if val.nil?
54 |
55 | case type
56 | when :integer then val.to_i rescue val ? 1 : 0
57 | when :float then val.to_f
58 | when :boolean then val.to_s =~ /^(t|1|y)/i ? true : false
59 | when :datetime then DateTime.parse(val.to_s)
60 | when :array then val.is_a?(Array) ? val : JSON.parse(val)
61 | when :hash then val.is_a?(Hash) ? val : JSON.parse(val)
62 | else val
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/lib/tasks/.keep
--------------------------------------------------------------------------------
/lib/tasks/github.rake:
--------------------------------------------------------------------------------
1 | namespace :github do
2 |
3 | task sync: 'sync:github'
4 |
5 | desc "Transitions Github users based on rules and current attributes"
6 | task transition_users: %w(environment sync:ldap sync:github) do
7 |
8 | # Make sure the majority of users have recently synced ldap and github
9 | min_sync_time = Time.now - [Rails.application.settings.rule_max_sync_age, 120].max + 120.seconds
10 |
11 | total_ldap_count = User.count
12 | synced_ldap_count = User.where('last_ldap_sync > ?', min_sync_time).count
13 | if synced_ldap_count < [total_ldap_count / 4, 1].max
14 | puts "Fewer than 25% of LDAP users (#{synced_ldap_count} of #{total_ldap_count}) meet minimum sync time. Skipping transition."
15 | exit 1
16 | end
17 |
18 | total_github_count = GithubUser.active.count
19 | synced_github_count = GithubUser.active.where('last_sync_at > ?', min_sync_time).count
20 | if synced_github_count < [total_github_count / 4, 1].max
21 | puts "Fewer than 25% of GitHub users (#{synced_github_count} of #{total_github_count}) meet minimum sync time. Skipping transition."
22 | exit 2
23 | end
24 |
25 | puts "Checking for users to disable..."
26 | executor = TransitionGithubUsers.new
27 | executor.run!
28 |
29 | disabled_users = executor.transitions.select { |u| u.disabled? }
30 | external_users = executor.transitions.select { |u| u.external? }
31 |
32 | executor.stats.each do |key, val|
33 | puts " #{key}: #{val}"
34 | end
35 |
36 | if disabled_users.empty? && external_users.empty?
37 | puts " No users to disable."
38 | end
39 | unless disabled_users.empty?
40 | puts " Disabled Github users: #{disabled_users.map { |u| u.login }.join(', ')}"
41 | end
42 | unless external_users.empty?
43 | puts " External Github users: #{external_users.map { |u| u.login }.join(', ')}"
44 | end
45 |
46 | unless executor.errors.empty?
47 | puts " Errors:"
48 | executor.errors.each do |error|
49 | puts " #{error}"
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/tasks/sync.rake:
--------------------------------------------------------------------------------
1 | desc "Synchronize LDAP and GitHub users"
2 | task sync: ['sync:ldap', 'sync:github']
3 |
4 | namespace :sync do
5 | desc "Synchronize Github users and teams"
6 | task github: :environment do
7 | puts "Synchronizing Github..."
8 | sync = GithubSynchronizer.new
9 | sync.run!
10 | sync.stats.each do |key, val|
11 | puts " #{key}: #{val}"
12 | end
13 | unless sync.errors.empty?
14 | puts " Errors:"
15 | sync.errors.each do |error|
16 | puts " #{error}"
17 | end
18 | end
19 | end
20 |
21 | desc "Synchronize Active Directory users"
22 | task ldap: :environment do
23 | puts "Synchronizing Active Directory..."
24 | sync = LdapSynchronizer.new
25 | sync.run!
26 | sync.stats.each do |key, val|
27 | puts " #{key}: #{val}"
28 | end
29 | unless sync.errors.empty?
30 | puts " Errors:"
31 | sync.errors.each do |error|
32 | puts " #{error}"
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/log/.keep
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/spec/controllers/dashboard_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe DashboardController, :type => :controller do
4 | before do
5 | sign_in
6 | configured
7 | end
8 |
9 | describe "GET 'index'" do
10 | it "returns http success" do
11 | get 'index'
12 | expect(response).to be_success
13 | end
14 |
15 | it 'redirects to setup wizard if application is not configured' do
16 | Rails.application.settings.configured = false
17 | get 'index'
18 | expect(response).to redirect_to(setup_url)
19 | end
20 |
21 | it 'returns a http error if an LDAP authentication error occurs' do
22 | allow(controller).to receive(:index).and_raise(DeviseLdapAuthenticatable::LdapException)
23 | get 'index'
24 | expect(response).to be_error
25 | end
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/spec/controllers/github_users_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe GithubUsersController, :type => :controller do
4 | before do
5 | sign_in(user)
6 | configured
7 | end
8 |
9 | let(:user) { create(:admin_user) }
10 | let(:github_user) { create(:github_user) }
11 |
12 | describe "GET index" do
13 | it "returns http success" do
14 | get :index
15 | expect(response).to be_success
16 | end
17 | end
18 |
19 | describe "GET show" do
20 | it "returns http success" do
21 | get :show, id: github_user.login
22 | expect(response).to be_success
23 | end
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/spec/controllers/setup/admin_user_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setup::AdminUserController, :type => :controller do
4 |
5 | let(:user) { create(:user) }
6 |
7 | before do
8 | @request.env["devise.mapping"] = Devise.mappings[:user]
9 | end
10 |
11 | describe "GET 'new'" do
12 | it "returns http success" do
13 | get 'new'
14 | expect(response).to be_success
15 | end
16 |
17 | it 'signs out existing users' do
18 | sign_in user
19 | get 'new'
20 | expect(controller).to_not be_signed_in
21 | end
22 | end
23 |
24 | describe "POST 'create'" do
25 | subject { post 'create', user: {username: user.username, password: 'foopass'} }
26 |
27 | it 'sets the admin user' do
28 | allow(controller.warden).to receive(:authenticate!).and_return(user)
29 | expect(subject).to be_redirect
30 | expect(user).to be_an_admin
31 | end
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/spec/controllers/setup/company_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setup::CompanyController, :type => :controller do
4 |
5 | describe "GET 'edit'" do
6 | it "returns http success" do
7 | get 'edit'
8 | expect(response).to be_success
9 | end
10 | end
11 |
12 | describe "PUT 'update'" do
13 | subject { put 'update', settings: {company: 'foocompany'} }
14 |
15 | it 'saves settings' do
16 | subject
17 | expect(Rails.application.settings.company).to eq('foocompany')
18 | end
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/spec/controllers/setup/email_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setup::EmailController, :type => :controller do
4 |
5 | describe "GET 'edit'" do
6 | it "returns http success" do
7 | get 'edit'
8 | expect(response).to be_success
9 | end
10 |
11 | it 'sets default email from company name' do
12 | allow(request).to receive(:host).and_return('localhost')
13 | Rails.application.settings.company = 'Example Corp'
14 | get 'edit'
15 | expect(assigns(:settings).email_from).to eq('github@example_corp.com')
16 | end
17 |
18 | it 'sets default email from url domain' do
19 | allow(request).to receive(:host).and_return('foocorp.com')
20 | get 'edit'
21 | expect(assigns(:settings).email_from).to eq('github@foocorp.com')
22 | end
23 | end
24 |
25 | describe "PUT 'update'" do
26 | subject { put 'update', settings: {smtp_address: 'localhost'} }
27 |
28 | it 'saves settings' do
29 | subject
30 | expect(Rails.application.settings.smtp_address).to eq('localhost')
31 | end
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/spec/controllers/setup/github_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setup::GithubController, :type => :controller do
4 |
5 | describe "GET 'edit'" do
6 | it "returns http success" do
7 | get 'edit'
8 | expect(response).to be_success
9 | end
10 |
11 | it 'sets default orgs' do
12 | Rails.application.settings.company = 'Example'
13 | get 'edit'
14 | expect(assigns(:settings).github_orgs).to eq(['example'])
15 | end
16 |
17 | it 'sets default teams' do
18 | Rails.application.settings.company = 'Example'
19 | get 'edit'
20 | expect(assigns(:settings).github_default_teams).to eq(['example-employees'])
21 | end
22 | end
23 |
24 | describe "PUT 'update'" do
25 | let(:settings) { {github_orgs: 'foocompany'} }
26 | subject { put 'update', settings: settings }
27 |
28 | it 'saves settings' do
29 | subject
30 | expect(Rails.application.settings.github_orgs).to eq(['foocompany'])
31 | end
32 |
33 | context 'with connect_github parameter' do
34 | it 'calls github_admin action' do
35 | expect(controller).to receive(:github_admin) { controller.redirect_to('foobar') }
36 | put 'update', settings: settings, connect_github: 'connect'
37 | end
38 | end
39 | end
40 |
41 | end
42 |
--------------------------------------------------------------------------------
/spec/controllers/setup/ldap_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setup::LdapController, :type => :controller do
4 |
5 | describe "GET 'edit'" do
6 | it "returns http success" do
7 | get 'edit'
8 | expect(response).to be_success
9 | end
10 |
11 | it 'redirects to settings if application is already configured' do
12 | Rails.application.settings.configured = true
13 | get 'edit'
14 | expect(response).to redirect_to(controller: '/settings', action: :edit)
15 | end
16 |
17 | it 'sets development defaults for localhost' do
18 | allow(request).to receive(:host).and_return('localhost')
19 | get 'edit'
20 | expect(assigns(:settings).ldap_base).to eq('dc=example,dc=com')
21 | end
22 | end
23 |
24 | describe "PUT 'update'" do
25 | subject { put 'update', settings: {ldap_host: 'foohost', ldap_port: 3389} }
26 | let(:ldap) { double('ldap', bind: true).as_null_object }
27 |
28 | before do
29 | allow(Net::LDAP).to receive(:new).and_return(ldap)
30 | end
31 |
32 | it 'saves settings' do
33 | subject
34 | expect(Rails.application.settings.ldap_host).to eq('foohost')
35 | end
36 |
37 | it 'tests ldap connection before saving' do
38 | expect(ldap).to receive(:bind).and_return(false)
39 | expect(subject).to_not be_redirect
40 | expect(assigns(:error)).to_not be_nil
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/controllers/setup/rules_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setup::RulesController, :type => :controller do
4 |
5 | describe "GET 'edit'" do
6 | it "returns http success" do
7 | get 'edit'
8 | expect(response).to be_success
9 | end
10 | end
11 |
12 | describe "PUT 'update'" do
13 | subject { put 'update', settings: {rule_max_sync_age: 60} }
14 |
15 | it 'saves settings' do
16 | subject
17 | expect(Rails.application.settings.rule_max_sync_age).to eq(60)
18 | end
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/spec/controllers/users_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe UsersController, :type => :controller do
4 | before do
5 | sign_in(user)
6 | configured
7 | end
8 |
9 | let(:user) { create(:admin_user, name: 'Admin User') }
10 |
11 | describe "GET 'index'" do
12 | it 'returns http success' do
13 | get 'index'
14 | expect(response).to be_success
15 | end
16 |
17 | it 'loads users in order' do
18 | get 'index'
19 | create(:user, name: 'Aaron Sorts First')
20 | names = assigns(:users).map { |user| user.name }
21 | expect(names).to eq(['Aaron Sorts First', 'Admin User'])
22 | end
23 | end
24 |
25 | describe "GET 'show'" do
26 | it "returns http success" do
27 | get 'show', id: user.username
28 | expect(response).to be_success
29 | end
30 |
31 | context 'with admin user' do
32 | it 'shows other users' do
33 | create(:user, username: 'otheruser', name: 'Other User')
34 | get 'show', id: 'otheruser'
35 | expect(response).to be_success
36 | expect(assigns(:user).username).to eq('otheruser')
37 | end
38 | end
39 |
40 | context 'with non-admin user' do
41 | let(:user) { create(:user, name: 'Regular User') }
42 |
43 | it 'shows own user' do
44 | get 'show', id: user.username
45 | expect(response).to be_success
46 | end
47 |
48 | it 'does not show other users' do
49 | create(:user, username: 'otheruser', name: 'Other User')
50 | get 'show', id: 'otheruser'
51 | expect(response).to be_forbidden
52 | end
53 | end
54 | end
55 |
56 | describe "GET 'edit'" do
57 | it "returns http success" do
58 | get 'edit', id: user.username
59 | expect(response).to be_success
60 | end
61 | end
62 |
63 | describe "PATCH 'edit'" do
64 | it "redirects after save" do
65 | patch 'update', id: user.username, user: {admin: 0}
66 | expect(response).to be_redirect
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/spec/factories/github_email.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :github_email do
3 | sequence(:address) { |n| "githubemail#{n}@example.com" }
4 | github_user
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/factories/github_organization_membership.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :github_organization_membership do
3 | sequence(:organization) { |n| "org#{n}" }
4 | github_user
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/factories/github_team.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :github_team do
3 | sequence(:slug) { |n| "githubteam#{n}" }
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/factories/github_user.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :github_user do
3 | sequence(:login) { |n| "githubber#{n}" }
4 |
5 | factory :github_user_with_emails do
6 | transient do
7 | emails_count 2
8 | end
9 |
10 | after(:create) do |github_user, evaluator|
11 | create_list(:github_email, evaluator.emails_count, github_user: github_user)
12 | end
13 | end
14 |
15 | factory :github_user_with_user do
16 | user
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/factories/user.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :user do
3 | sequence(:username) { |n| "fakeuser#{n}" }
4 |
5 | factory :user_with_github_users do
6 | transient do
7 | github_users_count 2
8 | end
9 | end
10 |
11 | factory :admin_user do
12 | admin true
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/helpers/application_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe ApplicationHelper do
4 | describe '#format_time' do
5 | it 'adds data-time attribute' do
6 | html = format_time(Time.now)
7 | expect(html).to include('data-time')
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/helpers/github_users_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe GithubUsersHelper do
4 | describe '#github_user_state_label' do
5 | let(:github_user) { build(:github_user) }
6 |
7 | it 'adds danger label for disabled users' do
8 | github_user.state = 'disabled'
9 | html = github_user_state_label(github_user)
10 | expect(html).to include('label-danger')
11 | end
12 |
13 | it 'adds info label for external users' do
14 | github_user.state = 'external'
15 | html = github_user_state_label(github_user)
16 | expect(html).to include('label-info')
17 | end
18 |
19 | it 'adds info label for excluded users' do
20 | github_user.state = 'excluded'
21 | html = github_user_state_label(github_user)
22 | expect(html).to include('label-info')
23 | end
24 |
25 | it 'adds warning label for unknown users' do
26 | github_user.state = 'unknown'
27 | html = github_user_state_label(github_user)
28 | expect(html).to include('label-warning')
29 | end
30 |
31 | it 'adds success label for enabled users' do
32 | github_user.state = 'enabled'
33 | html = github_user_state_label(github_user)
34 | expect(html).to include('label-success')
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/jobs/connect_github_user_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe ConnectGithubUserJob do
4 | subject(:job) { ConnectGithubUserJob.new }
5 | subject(:status) { ConnectGithubUserStatus.new(step: :grant) }
6 |
7 | let(:oauth) { double('oauth', auth_code: double(get_token: oauth_token)) }
8 | let(:oauth_token) { double('oauth_token', token: 'footoken') }
9 | let(:octokit) { double('octokit', user: ghuser) }
10 | let(:ghuser) { double(id: 1337, login: 'githubuser') }
11 | let(:github_user) { build(:github_user, id: 1337) }
12 |
13 | before do
14 | allow(job).to receive(:oauth_client).and_return(oauth)
15 | allow(GithubUser).to receive(:find_or_initialize_by).and_return(github_user)
16 | allow(github_user).to receive(:sync!) { github_user.save! }
17 | allow(github_user).to receive(:add_to_organizations).and_return(true)
18 | allow(github_user).to receive(:do_enable)
19 | allow(github_user).to receive(:do_disable)
20 | allow(github_user).to receive(:do_notify_disabled)
21 | allow(Octokit::Client).to receive(:new).and_return(octokit)
22 | end
23 |
24 | it 'validates OAuth code' do
25 | expect(job).to receive(:oauth_process_auth_code).and_return(github_user)
26 | job.perform(status)
27 | expect(status.status).to eq(:complete)
28 | end
29 |
30 | it 'adds user to organzations' do
31 | expect(github_user).to receive(:add_to_organizations).and_return(true)
32 | job.perform(status)
33 | expect(status.status).to eq(:complete)
34 | end
35 |
36 | it 'enables the user' do
37 | expect(github_user).to receive(:enable)
38 | job.perform(status)
39 | expect(status.status).to eq(:complete)
40 | end
41 |
42 | it 'stores error if OAuth fails' do
43 | oauth_response = double.as_null_object
44 | expect(job).to receive(:oauth_process_auth_code).and_raise(OAuth2::Error.new(oauth_response))
45 | job.perform(status)
46 | expect(status.status).to eq(:error)
47 | end
48 |
49 | it 'stores error if add_to_organizations fails' do
50 | expect(github_user).to receive(:add_to_organizations).and_return(false)
51 | job.perform(status)
52 | expect(status.status).to eq(:error)
53 | end
54 |
55 | it 'stores error if unexpected error occurs' do
56 | allow(github_user).to receive(:add_to_organizations).and_raise('fooerror')
57 | job.perform(status)
58 | expect(status.status).to eq(:error)
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/lib/github_connector/settings_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe GithubConnector::Settings do
4 |
5 | subject(:settings) { GithubConnector::Settings.new }
6 |
7 | describe '#apply_to_action_mailer' do
8 | it 'applies config to ActionMailer::Base' do
9 | settings.smtp_address = 'foohost'
10 | settings.email_base_url = 'https://localhost:443/'
11 | settings.email_from = 'github@fooemail'
12 | settings.email_reply_to = ''
13 | settings.apply_to_action_mailer
14 | expect(ActionMailer::Base.smtp_settings[:address]).to eq('foohost')
15 | expect(ActionMailer::Base.default_url_options).to eq({host: 'localhost', protocol: 'https'})
16 | expect(ActionMailer::Base.default[:from]).to eq('github@fooemail')
17 | expect(ActionMailer::Base.default.keys).to_not include(:reply_to)
18 | end
19 | end
20 |
21 | describe '#email_keys' do
22 | it 'returns a list of email keys' do
23 | expect(settings.email_keys).to eq(%i(email_base_url email_from email_reply_to))
24 | end
25 | end
26 |
27 | describe '#email_config' do
28 | before do
29 | Setting.create(key: :email_from, value: 'fooemail@example.com')
30 | end
31 |
32 | it 'returns hash with email_ key prefixes removed' do
33 | config = settings.email_config
34 | expect(config).to have_key(:from)
35 | expect(config).to_not have_key(:email_from)
36 | end
37 | end
38 |
39 | describe '#github_admin_oauth_scope' do
40 | it 'includes the user scope' do
41 | expect(settings).to receive(:github_user_oauth_scope).and_return('foouser:fooscope')
42 | expect(settings.github_admin_oauth_scope).to include('foouser:fooscope')
43 | end
44 |
45 | it 'includes admin:org' do
46 | expect(settings.github_admin_oauth_scope).to include('admin:org')
47 | end
48 | end
49 |
50 | describe '#github_user_oauth_scope' do
51 | it 'includes required scopes' do
52 | expect(settings.github_user_oauth_scope).to include('user:email')
53 | expect(settings.github_user_oauth_scope).to include('read:public_key')
54 | expect(settings.github_user_oauth_scope).to include('write:org')
55 | end
56 | end
57 |
58 | describe '#ldap_keys' do
59 | it 'returns a list of ldap keys' do
60 | expect(settings.ldap_keys).to eq(%i(ldap_host ldap_port ldap_ssl ldap_admin_user ldap_admin_password ldap_attribute ldap_base))
61 | end
62 | end
63 |
64 | describe '#ldap_config' do
65 | before do
66 | Setting.create(key: :ldap_host, value: 'localhost')
67 | end
68 |
69 | it 'returns hash with ldap_ key prefixes removed' do
70 | config = settings.ldap_config
71 | expect(config).to have_key('host')
72 | expect(config).to_not have_key('ldap_host')
73 | end
74 | end
75 |
76 | describe '#smtp_keys' do
77 | it 'returns a list of smtp keys' do
78 | expect(settings.smtp_keys).to eq(%i(smtp_address smtp_port smtp_enable_starttls_auto smtp_user_name smtp_password smtp_authentication smtp_domain))
79 | end
80 | end
81 |
82 | describe '#smtp_config' do
83 | before do
84 | Setting.create(key: :smtp_address, value: 'localhost')
85 | end
86 |
87 | it 'returns hash with smtp_ key prefixes removed' do
88 | config = settings.smtp_config
89 | expect(config).to have_key(:address)
90 | expect(config).to_not have_key(:smtp_address)
91 | end
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/spec/lib/ldap_synchronizer_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe LdapSynchronizer do
4 |
5 | subject(:synchronizer) { LdapSynchronizer.new([user]) }
6 | let(:user) { build(:user) }
7 |
8 | before do
9 | allow(user).to receive(:sync_from_ldap).and_return(true)
10 | end
11 |
12 | describe '#sync_users' do
13 | it 'calls sync_from_ldap' do
14 | expect(user).to receive(:sync_from_ldap).and_return(true)
15 | expect(synchronizer.sync_users).to eq(true)
16 | end
17 |
18 | it 'continues if errors occur' do
19 | allow(synchronizer).to receive(:users).and_raise('foo error')
20 | expect(synchronizer.sync_users).to eq(false)
21 | expect(synchronizer.errors).to be_a(Array)
22 | expect(synchronizer.errors).to_not be_empty
23 | end
24 |
25 | it 'continues if errors occur in threads' do
26 | allow(user).to receive(:sync_from_ldap).and_raise('foo error')
27 | expect(synchronizer.sync_users).to eq(false)
28 | expect(synchronizer.errors).to be_a(Array)
29 | expect(synchronizer.errors).to_not be_empty
30 | end
31 |
32 | it 'counts sync errors as errors' do
33 | allow(user).to receive(:ldap_sync_error).and_return('foo error')
34 | expect(synchronizer.sync_users).to eq(false)
35 | expect(synchronizer.errors).to be_a(Array)
36 | expect(synchronizer.errors.first).to include('foo error')
37 | end
38 | end
39 |
40 | describe '#run!' do
41 | it 'synchronizes users' do
42 | expect(synchronizer).to receive(:sync_users)
43 | synchronizer.run!
44 | end
45 |
46 | it 'returns true if successful' do
47 | expect(synchronizer.run!).to eq(true)
48 | end
49 |
50 | it 'returns false if errors occurred' do
51 | allow(user).to receive(:sync_from_ldap).and_raise("foo error")
52 | expect(synchronizer.run!).to eq(false)
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/lib/rules/active_ldap_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::ActiveLdap do
4 | subject(:rule) { Rules::ActiveLdap.new(github_user) }
5 | let(:user) { github_user.user }
6 | let(:github_user) { build(:github_user_with_user) }
7 | let(:settings) { double }
8 |
9 | before do
10 | allow(described_class).to receive(:settings).and_return(settings)
11 | end
12 |
13 | it 'is valid for a normal account' do
14 | user.ldap_account_control = 512
15 | expect(rule).to be_valid
16 | end
17 |
18 | it 'is not valid when account is disabled' do
19 | user.ldap_account_control = 514
20 | expect(rule).to_not be_valid
21 | end
22 |
23 | it 'is not valid without a User' do
24 | github_user.user = nil
25 | expect(rule).to_not be_valid
26 | end
27 |
28 | it 'does not notify' do
29 | expect(rule).to_not be_notify
30 | end
31 |
32 | it 'is not required for external users' do
33 | expect(rule).to_not be_required_for_external
34 | end
35 |
36 | describe '#error_msg' do
37 | it 'returns a generic error message' do
38 | github_user.user = nil
39 | expect(rule.error_msg).to be_a(String)
40 | expect(rule.error_msg).to include('criteria')
41 | end
42 |
43 | it 'returns an account disabled error message' do
44 | user.ldap_account_control = User::AccountControl::ACCOUNT_DISABLED
45 | expect(rule.error_msg).to include('disabled')
46 | end
47 |
48 | #it 'returns a password expired error message' do
49 | # user.ldap_account_control = User::AccountControl::PASSWORD_EXPIRED
50 | # expect(rule.error_msg).to include('password')
51 | #end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/lib/rules/base_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::Base do
4 | class TestRule < Rules::Base
5 | end
6 |
7 | subject(:rule) { TestRule.new(user) }
8 | let(:user) { double }
9 |
10 | it 'does not implement #result' do
11 | expect { rule.result }.to raise_error(NotImplementedError)
12 | end
13 |
14 | it 'notifies by default' do
15 | expect(rule.notify?).to eq(true)
16 | end
17 |
18 | it 'is required for external users by default' do
19 | expect(rule).to be_required_for_external
20 | end
21 |
22 | it 'converts class name to a rule name' do
23 | expect(rule.name).to eq('test_rule')
24 | end
25 |
26 | it 'references the application settings singleton' do
27 | expect(Rails.application).to receive(:settings).and_call_original
28 | expect(rule.settings).to be_a(GithubConnector::Settings)
29 | end
30 |
31 | it 'returns an error message' do
32 | expect(rule.error_msg).to be_a(String)
33 | expect(rule.error_msg).to_not be_empty
34 | end
35 |
36 | it 'is enabled by default' do
37 | expect(TestRule).to be_enabled
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/lib/rules/email_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::Email do
4 | subject(:rule) { Rules::Email.new(github_user) }
5 | let(:github_user) { create(:github_user_with_emails, user: user) }
6 | let(:user) { create(:user) }
7 | let(:settings) { double(rule_email_regex: regex) }
8 |
9 | before do
10 | allow(described_class).to receive(:settings).and_return(settings)
11 | end
12 |
13 | context 'with email regex' do
14 | let(:regex) { '@example\.com$' }
15 |
16 | it 'is enabled' do
17 | expect(described_class).to be_enabled
18 | end
19 |
20 | it 'is valid when regex matches' do
21 | expect(rule).to be_valid
22 | end
23 |
24 | it "is not valid when regex doesn't match" do
25 | github_email = github_user.emails.last
26 | github_email.address = 'bsimpson@example.org'
27 | github_email.save
28 | expect(rule).to_not be_valid
29 | end
30 |
31 | it 'does not check ldap address' do
32 | user.email = 'bsimpson@example.org'
33 | expect(rule).to be_valid
34 | end
35 |
36 | it 'is not required for external users' do
37 | expect(rule).to_not be_required_for_external
38 | end
39 |
40 | it 'returns an error message' do
41 | github_email = github_user.emails.last
42 | github_email.address = 'bsimpson@example.org'
43 | github_email.save
44 | expect(rule.error_msg).to be_a(String)
45 | expect(rule.error_msg).to include('bsimpson@example.org')
46 | end
47 | end
48 |
49 | context 'without email regex' do
50 | let(:regex) { nil }
51 |
52 | it 'is not enabled' do
53 | expect(described_class).to_not be_enabled
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/lib/rules/github_mfa_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::GithubMfa do
4 | subject(:rule) { Rules::GithubMfa.new(github_user) }
5 | let(:github_user) { build(:github_user_with_user) }
6 | let(:user) { github_user.user }
7 | let(:settings) { double }
8 |
9 | before do
10 | allow(described_class).to receive(:settings).and_return(settings)
11 | end
12 |
13 | it 'is valid when MFA is enabled' do
14 | github_user.mfa = true
15 | expect(rule).to be_valid
16 | end
17 |
18 | it 'is invaid when MFA is disabled' do
19 | github_user.mfa = false
20 | expect(rule).to_not be_valid
21 | end
22 |
23 | it 'is invalid when MFA is unknown' do
24 | github_user.mfa = nil
25 | expect(rule).to_not be_valid
26 | end
27 |
28 | it 'is required for external users' do
29 | expect(rule).to be_required_for_external
30 | end
31 |
32 | it 'returns an error message' do
33 | expect(rule.error_msg).to be_a(String)
34 | expect(rule.error_msg).to include('factor')
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/spec/lib/rules/github_oauth_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::GithubOauth do
4 | subject(:rule) { Rules::GithubOauth.new(github_user) }
5 | let(:github_user) { build(:github_user_with_user) }
6 | let(:user) { github_user.user }
7 | let(:settings) { double }
8 |
9 | before do
10 | allow(described_class).to receive(:settings).and_return(settings)
11 | github_user.token = 'footoken'
12 | end
13 |
14 | it 'is invalid when GitHub token is missing' do
15 | github_user.token = nil
16 | expect(rule).to_not be_valid
17 | end
18 |
19 | it 'is invalid with notoken GitHub error' do
20 | github_user.sync_error = 'notoken'
21 | expect(rule).to_not be_valid
22 | end
23 |
24 | it 'is invalid with unauthorized GitHub error' do
25 | github_user.sync_error = 'unauthorized'
26 | expect(rule).to_not be_valid
27 | end
28 |
29 | it 'is valid with no errors' do
30 | expect(rule).to be_valid
31 | end
32 |
33 | it 'is valid with GitHub server error' do
34 | github_user.sync_error = 'internal_server_error'
35 | expect(rule).to be_valid
36 | end
37 |
38 | it 'is not required for external users' do
39 | expect(rule).to_not be_required_for_external
40 | end
41 |
42 | it 'returns an error message when a token is missing' do
43 | github_user.token = nil
44 | expect(rule.error_msg).to be_a(String)
45 | expect(rule.error_msg.downcase).to include('missing')
46 | end
47 |
48 | it 'returns an error message when a token is missing' do
49 | github_user.sync_error = 'unauthorized'
50 | expect(rule.error_msg).to be_a(String)
51 | expect(rule.error_msg.downcase).to include('invalid')
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/lib/rules/last_github_sync_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::LastGithubSync do
4 | subject(:rule) { described_class.new(github_user) }
5 | let(:user) { github_user.user }
6 | let(:github_user) { build(:github_user_with_user) }
7 | let(:settings) { double(rule_max_sync_age: max_sync_age) }
8 |
9 | before do
10 | allow(described_class).to receive(:settings).and_return(settings)
11 | end
12 |
13 | context 'with max sync setting' do
14 | let(:max_sync_age) { 86400 }
15 |
16 | it 'is enabled' do
17 | expect(described_class).to be_enabled
18 | end
19 |
20 | it 'is valid when Github was recently synced' do
21 | github_user.last_sync_at = Time.now
22 | expect(rule).to be_valid
23 | end
24 |
25 | it 'is not valid when Github sync is out of date' do
26 | github_user.last_sync_at = Time.now - 2.days
27 | expect(rule).to_not be_valid
28 | end
29 |
30 | it 'is not valid when GitHub sync date is missing' do
31 | github_user.last_sync_at = nil
32 | expect(rule).to_not be_valid
33 | end
34 |
35 | it 'is required for external users' do
36 | expect(rule).to be_required_for_external
37 | end
38 |
39 | describe '#error_msg' do
40 | it 'returns an error message if GitHub user has never synced' do
41 | github_user.last_sync_at = nil
42 | expect(rule.error_msg).to include('never')
43 | end
44 |
45 | it 'returns an error message if GitHub user is too old' do
46 | github_user.last_sync_at = Time.now - 2.days
47 | expect(rule.error_msg).to include('old')
48 | end
49 | end
50 |
51 | end
52 |
53 | context 'without max sync setting' do
54 | let(:max_sync_age) { nil }
55 |
56 | it 'is not enabled' do
57 | expect(described_class).to_not be_enabled
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/lib/rules/last_ldap_sync_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules::LastLdapSync do
4 | subject(:rule) { described_class.new(github_user) }
5 | let(:user) { github_user.user }
6 | let(:github_user) { build(:github_user_with_user) }
7 | let(:settings) { double(rule_max_sync_age: max_sync_age) }
8 |
9 | before do
10 | allow(described_class).to receive(:settings).and_return(settings)
11 | end
12 |
13 | context 'with max sync setting' do
14 | let(:max_sync_age) { 86400 }
15 |
16 | it 'is enabled' do
17 | expect(described_class).to be_enabled
18 | end
19 |
20 | it 'is valid when Active Directory was recently synced' do
21 | user.last_ldap_sync = Time.now
22 | expect(rule).to be_valid
23 | end
24 |
25 | it 'is not valid when Active Directory sync is out of date' do
26 | user.last_ldap_sync = Time.now - 2.days
27 | expect(rule).to_not be_valid
28 | end
29 |
30 | it 'is not valid when Active Directory sync date is missing' do
31 | user.last_ldap_sync = nil
32 | expect(rule).to_not be_valid
33 | end
34 |
35 | it 'is not required for external users' do
36 | expect(rule).to_not be_required_for_external
37 | end
38 |
39 | describe '#error_msg' do
40 | it 'returns an error message if LDAP user doesn\'t exist' do
41 | github_user.user = nil
42 | expect(rule.error_msg).to include('user')
43 | end
44 |
45 | it 'returns an error message if Active Directory has never synced' do
46 | user.last_ldap_sync = nil
47 | expect(rule.error_msg).to include('never')
48 | end
49 |
50 | it 'returns an error message if Active Directory is too old' do
51 | user.last_ldap_sync = Time.now - 2.days
52 | expect(rule.error_msg).to include('old')
53 | end
54 | end
55 |
56 | end
57 |
58 | context 'without max sync setting' do
59 | let(:max_sync_age) { nil }
60 |
61 | it 'is not enabled' do
62 | expect(described_class).to_not be_enabled
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/lib/rules_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe Rules do
4 | it 'returns default rules' do
5 | rules = [
6 | Rules::Email,
7 | Rules::LastGithubSync,
8 | Rules::LastLdapSync,
9 | Rules::ActiveLdap,
10 | Rules::GithubMfa
11 | ]
12 |
13 | rules.each do |rule_klass|
14 | allow(rule_klass).to receive(:enabled?).and_return(true)
15 | end
16 |
17 | rules.each do |rule_klass|
18 | expect(Rules.enabled_rules).to include(rule_klass)
19 | end
20 | end
21 |
22 | it 'returns instantiated objects for a specific user' do
23 | user = build(:github_user)
24 | rule = Rules.for_github_user(user).first
25 | expect(rule).to be_a(Rules::Base)
26 | expect(rule.github_user).to eq(user)
27 | end
28 |
29 | describe Rules::Iterator do
30 | let(:user) { double(:user) }
31 | let(:rules) {[
32 | double(:rule1, name: 'rule1', valid?: false, required_for_external?: true),
33 | double(:rule2, name: 'rule2', valid?: false, required_for_external?: false),
34 | double(:rule3, name: 'rule3', valid?: true, required_for_external?: true),
35 | ]}
36 | let(:iterator) { described_class.new(rules) }
37 |
38 | it 'filters for failing rules' do
39 | expect(iterator.failing.map(&:name)).to eq(%w(rule1 rule2))
40 | end
41 |
42 | it 'filters for passing rules' do
43 | expect(iterator.passing.map(&:name)).to eq(%w(rule3))
44 | end
45 |
46 | it 'filters for external rules' do
47 | expect(iterator.external.map(&:name)).to eq(%w(rule1 rule3))
48 | end
49 |
50 | it 'allows chaining filters' do
51 | expect(iterator.failing.external.map(&:name)).to eq(%w(rule1))
52 | end
53 |
54 | it 'adds filters to clones without filtering original' do
55 | iterator2 = iterator.dup
56 | expect(iterator2.external.map(&:name)).to eq(%w(rule1 rule3))
57 | expect(iterator.map(&:name)).to eq(%w(rule1 rule2 rule3))
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/mailers/user_mailer_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | RSpec.describe UserMailer, :type => :mailer do
4 |
5 | before do
6 | Rails.application.settings.email_base_url = 'http://localhost:3000'
7 | end
8 |
9 | describe '#access_revoked' do
10 | subject(:mail) { UserMailer.access_revoked(user, github_user) }
11 |
12 | let(:user) { build(:user) }
13 | let(:github_user) { build(:github_user, user: user) }
14 |
15 | it 'renders subject' do
16 | expect(mail.subject).to eq('GitHub Access Revoked')
17 | end
18 |
19 | it 'renders html' do
20 | expect(mail).to be_multipart
21 | expect(mail.html_part.body).to include('GitHub access revoked!')
22 | end
23 |
24 | it 'renders plaintext' do
25 | expect(mail).to be_multipart
26 | expect(mail.text_part.body).to include('GitHub access revoked!')
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/models/connect_github_user_status_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe ConnectGithubUserStatus do
4 | subject(:status) { described_class.new(step: :request, status: :running) }
5 | let(:step) { :request }
6 |
7 | it 'computes completed steps' do
8 | expect(status.step_complete?(:add)).to eq(false)
9 | expect(status.steps_completed).to be_empty
10 | status.step = :add
11 | expect(status.step_complete?(:request)).to eq(true)
12 | status.status = :complete
13 | expect(status.step_complete?(:teams)).to eq(true)
14 | end
15 |
16 | it 'computes disabled steps' do
17 | expect(status.step_disabled?(:grant)).to eq(true)
18 | expect(status.step_disabled?(:create)).to eq(false)
19 | end
20 |
21 | it 'computes in progress status' do
22 | expect(status.in_progress?).to eq(true)
23 | status.status = :complete
24 | expect(status.in_progress?).to eq(false)
25 | end
26 |
27 | it 'computes complete status' do
28 | expect(status.complete?).to eq(false)
29 | status.status = :complete
30 | expect(status.complete?).to eq(true)
31 | end
32 |
33 | it 'computes error steps' do
34 | expect(status.step_error?(:request)).to eq(false)
35 | status.status = :error
36 | expect(status.step_error?(:request)).to eq(true)
37 | expect(status.step_error?(:create)).to eq(false)
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/models/github_team_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe GithubTeam do
4 | subject(:team) { described_class.new(id: 1) }
5 |
6 | context 'with GitHub' do
7 | let(:github_admin) { double('github_admin') }
8 | let(:gh_teams) {{
9 | 1 => {id: 1, name: 'My Team 1', slug: 'my_team_1', organization: 'org1'},
10 | 5 => {id: 5, name: 'My Team 5', slug: 'my_team_5', organization: 'org1'},
11 | }}
12 | let(:team_members) {{
13 | 'hsimpson' => {login: 'hsimpson', name: 'Homer Simpson'},
14 | 'msimpson' => {login: 'msimpson', name: 'Marge Simpson'},
15 | }}
16 |
17 | before do
18 | allow(team).to receive(:github_admin).and_return(github_admin)
19 | allow(github_admin).to receive(:teams).and_return(gh_teams)
20 | allow(github_admin).to receive(:team) do |team_id|
21 | gh_teams.values.find { |t| t[:id] == team_id || t[:slug] == team_id }
22 | end
23 | allow(github_admin).to receive(:team_members).and_return(team_members)
24 | end
25 |
26 | it 'synchronizes team information' do
27 | team.sync
28 | expect(team.name).to eq('My Team 1')
29 | expect(team.organization).to eq('org1')
30 | expect(team.slug).to eq('my_team_1')
31 | end
32 |
33 | it 'synchronizes added members' do
34 | create(:github_user, login: 'hsimpson')
35 | create(:github_user, login: 'msimpson')
36 | team.sync!
37 | expect(team.github_users.size).to eq(2)
38 | members = team.github_users.map { |t| t.login }
39 | expect(members).to include('hsimpson', 'msimpson')
40 | end
41 |
42 | it 'synchronizes removed members' do
43 | team.github_users << create(:github_user, login: 'foouser')
44 | team.sync
45 | members = team.github_users.map { |t| t.login }
46 | expect(members).to_not include('foouser')
47 | end
48 |
49 | it 'only saves if information changed' do
50 | team.sync
51 | expect(team).to_not receive(:save)
52 | expect(team).to_not receive(:save!)
53 | expect(team.sync).to eq(true)
54 | end
55 |
56 | it 'only saves if information changed' do
57 | team.sync
58 | expect(team).to_not receive(:save)
59 | expect(team).to_not receive(:save!)
60 | expect(team.sync).to eq(true)
61 | end
62 | end
63 |
64 | it 'returns a GithubAdmin client' do
65 | expect(team.github_admin).to be_a(GithubAdmin)
66 | end
67 |
68 | it 'returns a "full" slug' do
69 | team.organization = "org1"
70 | team.slug = "my_team_1"
71 | expect(team.full_slug).to eq("org1/my_team_1")
72 | end
73 |
74 | it 'finds by "full" slug' do
75 | team.organization = "org1"
76 | team.slug = "my_team_1"
77 | team.save
78 |
79 | found_team = described_class.find_by_full_slug('org1/my_team_1')
80 | expect(found_team).to be_a(GithubTeam)
81 | expect(found_team.id).to eq(team.id)
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/spec/models/setting_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Setting, :type => :model do
4 | end
5 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require 'spec_helper'
4 | require File.expand_path("../../config/environment", __FILE__)
5 | require 'rspec/rails'
6 |
7 | # Requires supporting ruby files with custom matchers and macros, etc, in
8 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
9 | # run as spec files by default. This means that files in spec/support that end
10 | # in _spec.rb will both be required and run as specs, causing the specs to be
11 | # run twice. It is recommended that you do not name files matching this glob to
12 | # end with _spec.rb. You can configure this pattern with with the --pattern
13 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
14 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
15 |
16 | # Checks for pending migrations before tests are run.
17 | # If you are not using ActiveRecord, you can remove this line.
18 | ActiveRecord::Migration.maintain_test_schema!
19 |
20 | RSpec.configure do |config|
21 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
22 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
23 |
24 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
25 | # examples within a transaction, remove the following line or assign false
26 | # instead of true.
27 | config.use_transactional_fixtures = false
28 |
29 | # RSpec Rails can automatically mix in different behaviours to your tests
30 | # based on their file location, for example enabling you to call `get` and
31 | # `post` in specs under `spec/controllers`.
32 | #
33 | # You can disable this behaviour by removing the line below, and instead
34 | # explicitly tag your specs with their type, e.g.:
35 | #
36 | # RSpec.describe UsersController, :type => :controller do
37 | # # ...
38 | # end
39 | #
40 | # The different available types are documented in the features, such as in
41 | # https://relishapp.com/rspec/rspec-rails/docs
42 | config.infer_spec_type_from_file_location!
43 |
44 | config.include FactoryGirl::Syntax::Methods
45 | config.include Devise::TestHelpers, type: :controller
46 | config.include Devise::TestHelpers, type: :view
47 | config.include ControllerHelpers, type: :controller
48 |
49 | config.before(:suite) do
50 | FactoryGirl.lint
51 | DatabaseCleaner.clean_with(:deletion)
52 | end
53 |
54 | DatabaseCleaner.strategy = :deletion
55 | config.around(:each) do |example|
56 | DatabaseCleaner.cleaning do
57 | example.run
58 | end
59 | end
60 |
61 | end
62 |
--------------------------------------------------------------------------------
/spec/support/controller_helpers.rb:
--------------------------------------------------------------------------------
1 | module ControllerHelpers
2 | def sign_in(user=nil)
3 | user = create(:user) unless user
4 | super(user)
5 | end
6 |
7 | def configured
8 | Rails.application.settings.configured = true
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/views/connect/index.html.erb_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe "connect/index.html.erb", type: :view do
4 | let(:connect_status) { ConnectGithubUserStatus.new(step: :request) }
5 | let(:user) { build(:user) }
6 |
7 | before do
8 | assign(:connect_status, connect_status)
9 | allow(view).to receive(:current_user).and_return(user)
10 | end
11 |
12 | it 'renders' do
13 | render
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/views/layouts/application.html.erb_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe "layouts/application", type: :view do
4 | before do
5 | Rails.application.settings.configured = true
6 | assign(:navbar, GithubConnector::Navbar.new)
7 | end
8 |
9 | context 'without user' do
10 | it 'does not display login items' do
11 | render
12 | expect(rendered).to_not include('Logout')
13 | end
14 | end
15 |
16 | context 'with user' do
17 | let(:user) { create(:user) }
18 |
19 | before do
20 | sign_in(user)
21 | end
22 |
23 | it 'displays login items' do
24 | render
25 | expect(rendered).to include('Logout')
26 | end
27 |
28 | it 'does not display admin navigation' do
29 | render
30 | expect(rendered).to_not include('Settings')
31 | end
32 | end
33 |
34 | context 'with admin user' do
35 | let(:user) { create(:admin_user) }
36 |
37 | before do
38 | sign_in(user)
39 | end
40 |
41 | it 'displays admin navigation' do
42 | render
43 | expect(rendered).to include('Settings')
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/views/settings/edit.html.erb_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe "settings/edit.html.erb", type: :view do
4 | # We can't call this "settings" because ViewExampleGroup adds view helpers
5 | # to the spec -- the settings helper would take precedence over a let
6 | let(:test_settings) { GithubConnector::Settings.new.disconnect; }
7 | let(:section_partials) { SettingsController.new.send(:section_partials) }
8 | let(:user) { build(:user) }
9 |
10 | before do
11 | controller.extend(SettingsMixin)
12 | controller.instance_variable_set('@settings', test_settings)
13 | assign(:settings, test_settings)
14 | assign(:section_partials, section_partials)
15 | allow(view).to receive(:current_user).and_return(user)
16 | end
17 |
18 | it 'replaces existing password with placeholder' do
19 | test_settings.ldap_admin_password = 'foopass'
20 | test_settings.save
21 | render
22 | expect(rendered).to_not include('foopass')
23 | end
24 |
25 | it 'does not replace new password' do
26 | test_settings.ldap_admin_password = 'foopass'
27 | render
28 | expect(rendered).to include('foopass')
29 | end
30 |
31 | it 'replaces existing GitHub token with placeholder' do
32 | test_settings.github_admin_token = 'footoken'
33 | test_settings.save
34 | render
35 | expect(rendered).to_not include('footoken')
36 | end
37 |
38 | it 'does not replace new GitHub token' do
39 | test_settings.github_admin_token = 'footoken'
40 | render
41 | expect(rendered).to include('footoken')
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/vendor/assets/javascripts/.keep
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rapid7/github-connector/0de9978ad30d02cea43f986d248c39967d453896/vendor/assets/stylesheets/.keep
--------------------------------------------------------------------------------