Repository: gate-sso/gate Branch: master Commit: 958d5b920b07 Files: 370 Total size: 479.5 KB Directory structure: gitextract_ouflf_22/ ├── .deepsource.toml ├── .dockerignore ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── api_blueprint/ │ ├── bin/ │ │ └── dredd_server.sh │ ├── group.apib │ ├── hooks/ │ │ └── dredd_hooks.rb │ ├── user.apib │ └── vpns.apib ├── app/ │ ├── assets/ │ │ ├── config/ │ │ │ └── manifest.js │ │ ├── images/ │ │ │ └── .keep │ │ ├── javascripts/ │ │ │ ├── admin.coffee │ │ │ ├── api_resources.coffee │ │ │ ├── application.js │ │ │ ├── bootstrap.js.coffee │ │ │ ├── group.coffee │ │ │ ├── groups.coffee │ │ │ ├── home.coffee │ │ │ ├── host_access_groups.coffee │ │ │ ├── host_machine_groups.coffee │ │ │ ├── host_machines.coffee │ │ │ ├── nss.coffee │ │ │ ├── omniauth_callbacks.coffee │ │ │ ├── profile.coffee │ │ │ ├── users.coffee │ │ │ ├── utilities.coffee │ │ │ └── viewport.js │ │ └── stylesheets/ │ │ ├── application.scss │ │ ├── bootstrap-social.scss │ │ ├── general.css │ │ ├── home.scss.erb │ │ ├── profile.css │ │ └── scaffolds.scss │ ├── clients/ │ │ └── data_dog_client.rb │ ├── controllers/ │ │ ├── admin_controller.rb │ │ ├── api/ │ │ │ └── v1/ │ │ │ ├── base_controller.rb │ │ │ ├── endpoints_controller.rb │ │ │ ├── groups_controller.rb │ │ │ ├── users_controller.rb │ │ │ └── vpns_controller.rb │ │ ├── api_resources_controller.rb │ │ ├── application_controller.rb │ │ ├── concerns/ │ │ │ └── .keep │ │ ├── groups_controller.rb │ │ ├── home_controller.rb │ │ ├── host_controller.rb │ │ ├── host_machine_groups_controller.rb │ │ ├── host_machines_controller.rb │ │ ├── nss_controller.rb │ │ ├── organisations_controller.rb │ │ ├── pings_controller.rb │ │ ├── profile_controller.rb │ │ ├── saml_idp_controller.rb │ │ ├── users/ │ │ │ ├── auth_controller.rb │ │ │ └── omniauth_callbacks_controller.rb │ │ ├── users_controller.rb │ │ ├── vpn_domain_name_servers_controller.rb │ │ └── vpns_controller.rb │ ├── helpers/ │ │ ├── admin_helper.rb │ │ ├── api_resources_helper.rb │ │ ├── application_helper.rb │ │ ├── group_helper.rb │ │ ├── groups_helper.rb │ │ ├── home_helper.rb │ │ ├── host_access_groups_helper.rb │ │ ├── host_machine_groups_helper.rb │ │ ├── host_machines_helper.rb │ │ ├── nss_helper.rb │ │ ├── omniauth_callbacks_helper.rb │ │ ├── profile_helper.rb │ │ └── users_helper.rb │ ├── lib/ │ │ ├── datadog.rb │ │ └── saml_app.rb │ ├── mailers/ │ │ └── .keep │ ├── models/ │ │ ├── .keep │ │ ├── access_token.rb │ │ ├── api_resource.rb │ │ ├── application_record.rb │ │ ├── concerns/ │ │ │ ├── .keep │ │ │ └── ms_chap_auth.rb │ │ ├── endpoint.rb │ │ ├── group.rb │ │ ├── group_admin.rb │ │ ├── group_association.rb │ │ ├── group_endpoint.rb │ │ ├── host.rb │ │ ├── host_access_group.rb │ │ ├── host_machine.rb │ │ ├── ip_address.rb │ │ ├── organisation.rb │ │ ├── saml_app_config.rb │ │ ├── user.rb │ │ ├── vpn.rb │ │ ├── vpn_domain_name_server.rb │ │ ├── vpn_group_association.rb │ │ ├── vpn_group_user_association.rb │ │ ├── vpn_search_domain.rb │ │ └── vpn_supplemental_match_domain.rb │ ├── validators/ │ │ └── email_validator.rb │ └── views/ │ ├── admin/ │ │ └── index.html.slim │ ├── api/ │ │ └── v1/ │ │ └── users/ │ │ └── show.json.jbuilder │ ├── api_resources/ │ │ ├── _api_resource.json.jbuilder │ │ ├── _form.html.slim │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ ├── index.json.jbuilder │ │ ├── new.html.slim │ │ ├── show.html.slim │ │ └── show.json.jbuilder │ ├── application/ │ │ ├── _admin.html.slim │ │ ├── _groups_header.html.slim │ │ └── _host_header.html.slim │ ├── common/ │ │ └── errors.json.jbuilder │ ├── groups/ │ │ ├── _form.html.slim │ │ ├── index.html.slim │ │ ├── new.html.slim │ │ └── show.html.slim │ ├── home/ │ │ └── index.html.slim │ ├── host_machines/ │ │ ├── index.html.slim │ │ ├── new.html.slim │ │ └── show.html.slim │ ├── layouts/ │ │ ├── application.html.slim │ │ ├── home.html.slim │ │ └── profile.html.slim.disabled │ ├── nss/ │ │ └── add_host.json.jbuilder │ ├── organisations/ │ │ ├── _form.html.slim │ │ ├── config_saml_app.html.slim │ │ ├── index.html.slim │ │ ├── new.html.slim │ │ ├── saml_apps/ │ │ │ └── _datadog.html.erb │ │ └── show.html.slim │ ├── profile/ │ │ ├── _group_search.html.slim │ │ ├── _user_search.html.slim │ │ ├── group_admin.html.slim │ │ ├── list.html.slim │ │ ├── public_key.html.slim │ │ ├── show.html.slim │ │ ├── user.html.slim │ │ └── user_admin.html.slim │ ├── saml_idp/ │ │ └── idp/ │ │ └── new.html.erb │ ├── users/ │ │ ├── _search.html.slim │ │ ├── index.html.slim │ │ ├── new.html.erb │ │ └── show.html.slim │ └── vpns/ │ ├── _form.html.slim │ ├── edit.html.slim │ ├── index.html.slim │ ├── new.html.slim │ └── show.html.slim ├── bin/ │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── update ├── config/ │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── database.yml │ ├── environment.rb │ ├── environments/ │ │ ├── development.rb │ │ ├── integration.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers/ │ │ ├── application_controller_renderer.rb │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── content_security_policy.rb │ │ ├── cookies_serializer.rb │ │ ├── devise.rb │ │ ├── dotenv.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── new_framework_defaults.rb │ │ ├── new_framework_defaults_7_0.rb │ │ ├── permissions_policy.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales/ │ │ ├── devise.en.yml │ │ ├── en.bootstrap.yml │ │ └── en.yml │ ├── newrelic.yml │ ├── puma.rb │ ├── redis.yml │ ├── routes.rb │ ├── schedule.rb │ ├── secrets.yml │ ├── spring.rb │ └── storage.yml ├── config.ru ├── db/ │ ├── migrate/ │ │ ├── 20160419122430_devise_create_users.rb │ │ ├── 20160419132647_add_provider_to_users.rb │ │ ├── 20160419144739_add_name_to_users.rb │ │ ├── 20160427123146_add_auth_key_to_user.rb │ │ ├── 20160427123233_add_provisioning_uri_to_user.rb │ │ ├── 20160519042340_add_active_to_users.rb │ │ ├── 20160519064340_add_default_value_to_users.rb │ │ ├── 20160615044834_create_hosts.rb │ │ ├── 20160615045052_add_admin_to_user.rb │ │ ├── 20160615112805_add_user_to_host.rb │ │ ├── 20160628140022_add_deleted_at_to_host.rb │ │ ├── 20160628140440_add_deleted_by_to_host.rb │ │ ├── 20160629043358_add_homedir_to_user.rb │ │ ├── 20160629043415_add_shell_to_user.rb │ │ ├── 20160629075435_create_groups.rb │ │ ├── 20160701090045_create_group_associations.rb │ │ ├── 20160701112600_add_deleted_properties_to_group.rb │ │ ├── 20160707115313_create_access_tokens.rb │ │ ├── 20160714115228_add_public_key_to_user.rb │ │ ├── 20160908081651_create_host_machines.rb │ │ ├── 20161003145832_create_host_access_groups.rb │ │ ├── 20170803140620_add_user_login_id_to_user.rb │ │ ├── 20171013115441_create_versions.rb │ │ ├── 20171016064705_remove_index_group_name.rb │ │ ├── 20171016071526_add_unique_index_on_groups_name.rb │ │ ├── 20171031060034_create_group_admin.rb │ │ ├── 20171031060217_add_foreign_key_ref_on_group_admin.rb │ │ ├── 20171031100758_create_vpns.rb │ │ ├── 20171031101026_create_vpn_group_association.rb │ │ ├── 20171031103518_add_foreign_key_ref_on_vpn_group_association.rb │ │ ├── 20171031113123_create_vpn_group_user_association.rb │ │ ├── 20171031121702_add_foreign_key_ref_on_vpn_group_user_association.rb │ │ ├── 20171102071909_add_ip_address_to_vpns.rb │ │ ├── 20171107114249_remove_url_from_vpns.rb │ │ ├── 20171108130234_add_user_id_to_access_token.rb │ │ ├── 20171108130353_add_foreign_key_ref_on_access_tokens.rb │ │ ├── 20171124090240_add_uuid_to_vpns.rb │ │ ├── 20171124114427_create_vpn_domain_name_servers.rb │ │ ├── 20171124114830_create_vpn_search_domains.rb │ │ ├── 20171124115925_create_vpn_supplemental_match_domains.rb │ │ ├── 20180104081814_add_product_name_to_users.rb │ │ ├── 20180202102206_create_saml_service_providers.rb │ │ ├── 20180214050204_add_api_key_to_host_machines.rb │ │ ├── 20180214052451_create_ip_addresses.rb │ │ ├── 20180214052644_add_host_machine_to_ip_address.rb │ │ ├── 20180219150818_add_description_to_group.rb │ │ ├── 20180222135930_add_access_key_to_host_machine.rb │ │ ├── 20180222140000_add_access_key_to_user.rb │ │ ├── 20180227051732_create_api_resources.rb │ │ ├── 20180301010021_add_user_to_api_resources.rb │ │ ├── 20180301010035_add_group_to_api_resources.rb │ │ ├── 20180306231200_add_deactivated_at_to_users.rb │ │ ├── 20180311082600_rename_access_key_in_api_resources.rb │ │ ├── 20180311161200_rename_token_in_access_tokens.rb │ │ ├── 20180318083000_create_indexes_to_speedup_nss_controller.rb │ │ ├── 20180613074108_create_organisations.rb │ │ ├── 20180613165050_drop_saml_service_providers.rb │ │ ├── 20180723175600_update_organisations_for_saml.rb │ │ ├── 20181002023107_add_default_admins_to_host_machines.rb │ │ ├── 20181016093315_create_saml_app_configs.rb │ │ ├── 20181208184236_add_fields_to_user.rb │ │ ├── 20190624024930_add_expiration_date_to_group_associations.rb │ │ ├── 20190820070910_create_endpoints.rb │ │ ├── 20190820075040_create_group_endpoints.rb │ │ ├── 20190820080624_add_foreign_key_ref_on_group_endpoints.rb │ │ ├── 20200113065717_add_sessions_table.rb │ │ ├── 20220926001848_add_service_name_to_active_storage_blobs.active_storage.rb │ │ ├── 20220926001849_create_active_storage_variant_records.active_storage.rb │ │ └── 20220926001850_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb │ ├── schema.rb │ ├── seeds/ │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ └── seeds.rb ├── docker-compose.yaml ├── docs/ │ ├── additional_setup.md │ ├── administration.md │ ├── dredd_setup.md │ ├── newrelic.md │ └── oauth_setup.md ├── dredd.yml ├── lib/ │ ├── assets/ │ │ └── .keep │ ├── tasks/ │ │ ├── .keep │ │ ├── app.rake │ │ ├── setup.rake │ │ ├── users.rake │ │ └── vpn.rake │ └── vpn/ │ ├── mobileconfig.erb │ ├── mobileconfig.rb │ └── namespace.rb ├── public/ │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── robots.txt ├── scripts/ │ ├── gen-client-conf │ └── gen-client-keys ├── setup.sh └── spec/ ├── clients/ │ └── data_dog_client_spec.rb ├── controllers/ │ ├── admin_controller_spec.rb │ ├── api/ │ │ └── v1/ │ │ ├── api_controller_spec.rb │ │ ├── endpoints_controller_spec.rb │ │ ├── groups_controller_spec.rb │ │ ├── users_controller_spec.rb │ │ └── vpns_controller_spec.rb │ ├── api_resources_controller_spec.rb │ ├── groups_controller_spec.rb │ ├── home_controller_spec.rb │ ├── host_machine_groups_controller_spec.rb │ ├── host_machines_controller_spec.rb │ ├── nss_controller_spec.rb │ ├── omniauth_callbacks_controller_spec.rb │ ├── organisations_controller_spec.rb │ ├── profile_controller_spec.rb │ ├── users/ │ │ └── auth_controller_spec.rb │ ├── users_controller_spec.rb │ └── vpns_controller_spec.rb ├── factories/ │ ├── access_tokens.rb │ ├── api_resources.rb │ ├── endpoints.rb │ ├── group_associations.rb │ ├── groups.rb │ ├── host_access_groups.rb │ ├── host_machine_groups.rb │ ├── host_machines.rb │ ├── ip_addresses.rb │ ├── organisations.rb │ ├── saml_app_configs.rb │ ├── user_host_access_groups.rb │ ├── users.rb │ └── vpns.rb ├── features/ │ ├── layout_spec.rb │ ├── organisations/ │ │ ├── add_user_saml_app_spec.rb │ │ ├── config_saml_app_spec.rb │ │ ├── create_spec.rb │ │ ├── list_spec.rb │ │ ├── list_user_saml_app_spec.rb │ │ ├── remove_user_saml_app_spec.rb │ │ ├── save_config_saml_app_spec.rb │ │ ├── setup_saml_spec.rb │ │ └── update_spec.rb │ ├── saml/ │ │ └── show_spec.rb │ └── users/ │ ├── create_user_spec.rb │ └── regenerate_auth_spec.rb ├── helpers/ │ └── application_helper_spec.rb ├── lib/ │ ├── datadog_spec.rb │ ├── saml_app_spec.rb │ └── tasks/ │ └── users_rake_spec.rb ├── models/ │ ├── access_token_spec.rb │ ├── api_resource_spec.rb │ ├── endpoint_spec.rb │ ├── group_association_spec.rb │ ├── group_endpoint_spec.rb │ ├── group_spec.rb │ ├── host_machine_spec.rb │ ├── host_spec.rb │ ├── ip_address_spec.rb │ ├── organisation_spec.rb │ ├── user_spec.rb │ └── vpn_spec.rb ├── rails_helper.rb ├── routing/ │ └── api_resources_routing_spec.rb ├── spec_helper.rb ├── support/ │ └── helpers/ │ └── x509_certificate_helper.rb └── views/ ├── api_resources/ │ ├── edit.html.slim_spec.rb │ ├── index.html.slim_spec.rb │ ├── new.html.slim_spec.rb │ └── show.html.slim_spec.rb ├── groups/ │ └── show.html.slim_spec.rb └── layouts/ └── home.html.slim_spec.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .deepsource.toml ================================================ version = 1 [[analyzers]] name = "ruby" enabled = true [[analyzers]] name = "javascript" enabled = true ================================================ FILE: .dockerignore ================================================ /log/* !/log/.keep /tmp .idea .env *.swp config/application.yml dump.rdb coverage .git ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle .local vendor/* # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal # Ignore all logfiles and tempfiles. /log/* !/log/.keep /tmp .idea .env .ruby-gemset *.swp config/application.yml public/assets/* dump.rdb coverage data/ ================================================ FILE: .rspec ================================================ --color --format documentation --require spec_helper ================================================ FILE: .rubocop.yml ================================================ --- require: - rubocop-rails - rubocop-performance - rubocop-faker AllCops: NewCops: enable Exclude: - config/initializers/*.rb - config/environments/*.rb - db/*.rb - app/workers/*.rb - app/services/**/*.rb - spec/services/*.rb - bin/* - Rakefile - lib/tasks/*.rake - spec/workers/*.rb - chef-repo/**/* Naming/AccessorMethodName: Description: Check the naming of accessor methods for get_/set_. Enabled: false Style/Alias: Description: Use alias_method instead of alias. StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method Enabled: false Style/ArrayJoin: Description: Use Array#join instead of Array#*. StyleGuide: https://github.com/bbatsov/ruby-style-guide#array-join Enabled: false Style/AsciiComments: Description: Use only ascii symbols in comments. StyleGuide: https://github.com/bbatsov/ruby-style-guide#english-comments Enabled: false Naming/AsciiIdentifiers: Description: Use only ascii symbols in identifiers. StyleGuide: https://github.com/bbatsov/ruby-style-guide#english-identifiers Enabled: false Style/Attr: Description: Checks for uses of Module#attr. StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr Enabled: false Metrics/BlockNesting: Description: Avoid excessive block nesting StyleGuide: https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count Enabled: false Style/CaseEquality: Description: Avoid explicit use of the case equality operator(===). StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-case-equality Enabled: false Style/CharacterLiteral: Description: Checks for uses of character literals. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-character-literals Enabled: false Style/ClassAndModuleChildren: Description: Checks style of children classes and modules. Enabled: true EnforcedStyle: compact Exclude: - config/application.rb Metrics/ClassLength: Description: Avoid classes longer than 100 lines of code. Enabled: false Metrics/ModuleLength: Description: Avoid modules longer than 100 lines of code. Enabled: false Style/ClassVars: Description: Avoid the use of class variables. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-class-vars Enabled: false Style/CollectionMethods: Enabled: true PreferredMethods: find: detect inject: reduce collect: map find_all: select Style/ColonMethodCall: Description: 'Do not use :: for method call.' StyleGuide: https://github.com/bbatsov/ruby-style-guide#double-colons Enabled: false Style/CommentAnnotation: Description: Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK, REVIEW). StyleGuide: https://github.com/bbatsov/ruby-style-guide#annotate-keywords Enabled: false Metrics/AbcSize: Description: A calculated magnitude based on number of assignments, branches, and conditions. Enabled: false Metrics/BlockLength: CountComments: true Max: 25 AllowedMethods: [] Exclude: - spec/**/* - config/environments/*.rb Metrics/CyclomaticComplexity: Description: A complexity metric that is strongly correlated to the number of test cases needed to validate a method. Enabled: false Rails/Delegate: Description: Prefer delegate method for delegations. Enabled: false Style/PreferredHashMethods: Description: Checks use of `has_key?` and `has_value?` Hash methods. StyleGuide: "#hash-key" Enabled: false Style/Documentation: Description: Document classes and non-namespace modules. Enabled: false Style/DoubleNegation: Description: Checks for uses of double negation (!!). StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang Enabled: false Style/EachWithObject: Description: Prefer `each_with_object` over `inject` or `reduce`. Enabled: false Style/EmptyLiteral: Description: Prefer literals to Array.new/Hash.new/String.new. StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash Enabled: false Style/Encoding: Enabled: false Style/EvenOdd: Description: Favor the use of Fixnum#even? && Fixnum#odd? StyleGuide: https://github.com/bbatsov/ruby-style-guide#predicate-methods Enabled: false Naming/FileName: Description: Use snake_case for source file names. StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files Enabled: false Style/FrozenStringLiteralComment: Description: Add the frozen_string_literal comment to the top of files to help transition from Ruby 2.3.0 to Ruby 3.0. Enabled: false Style/FormatString: Description: Enforce the use of Kernel#sprintf, Kernel#format or String#%. StyleGuide: https://github.com/bbatsov/ruby-style-guide#sprintf Enabled: false Style/GlobalVars: Description: Do not introduce global variables. StyleGuide: https://github.com/bbatsov/ruby-style-guide#instance-vars Reference: http://www.zenspider.com/Languages/Ruby/QuickRef.html Enabled: false Style/GuardClause: Description: Check for conditionals that can be replaced with guard clauses StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals Enabled: false Style/IfUnlessModifier: Description: Favor modifier if/unless usage when you have a single-line body. StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier Enabled: false Style/IfWithSemicolon: Description: Do not use if x; .... Use the ternary operator instead. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs Enabled: false Style/InlineComment: Description: Avoid inline comments. Enabled: false Style/Lambda: Description: Use the new lambda literal syntax for single-line blocks. StyleGuide: https://github.com/bbatsov/ruby-style-guide#lambda-multi-line Enabled: false Style/LambdaCall: Description: Use lambda.call(...) instead of lambda.(...). StyleGuide: https://github.com/bbatsov/ruby-style-guide#proc-call Enabled: false Style/LineEndConcatenation: Description: Use \ instead of + or << to concatenate two string literals at line end. Enabled: false Metrics/MethodLength: Description: Avoid methods longer than 10 lines of code. StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods Enabled: false Style/ModuleFunction: Description: Checks for usage of `extend self` in modules. StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function Enabled: false Style/MultilineBlockChain: Description: Avoid multi-line chains of blocks. StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks Enabled: false Style/NegatedIf: Description: Favor unless over if for negative conditions (or control flow or). StyleGuide: https://github.com/bbatsov/ruby-style-guide#unless-for-negatives Enabled: false Style/NegatedWhile: Description: Favor until over while for negative conditions. StyleGuide: https://github.com/bbatsov/ruby-style-guide#until-for-negatives Enabled: false Style/Next: Description: Use `next` to skip iteration instead of a condition at the end. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals Enabled: false Style/NilComparison: Description: Prefer x.nil? to x == nil. StyleGuide: https://github.com/bbatsov/ruby-style-guide#predicate-methods Enabled: false Style/Not: Description: Use ! instead of not. StyleGuide: https://github.com/bbatsov/ruby-style-guide#bang-not-not Enabled: false Style/NumericLiterals: Description: Add underscores to large numeric literals to improve their readability. StyleGuide: https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics Enabled: false Style/OneLineConditional: Description: Favor the ternary operator(?:) over if/then/else/end constructs. StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator Enabled: false Naming/BinaryOperatorParameterName: Description: When defining binary operators, name the argument other. StyleGuide: https://github.com/bbatsov/ruby-style-guide#other-arg Enabled: false Metrics/ParameterLists: Description: Avoid parameter lists longer than three or four parameters. StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params Enabled: false Style/PercentLiteralDelimiters: Description: Use `%`-literal delimiters consistently StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces Enabled: false Style/PerlBackrefs: Description: Avoid Perl-style regex back references. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers Enabled: false Naming/PredicateName: Description: Check the names of predicate methods. StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark ForbiddenPrefixes: - is_ Exclude: - spec/**/* Style/Proc: Description: Use proc instead of Proc.new. StyleGuide: https://github.com/bbatsov/ruby-style-guide#proc Enabled: false Style/RaiseArgs: Description: Checks the arguments passed to raise/fail. StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages Enabled: false Style/RegexpLiteral: Description: Use / or %r around regular expressions. StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-r Enabled: false Style/SelfAssignment: Description: Checks for places where self-assignment shorthand should have been used. StyleGuide: https://github.com/bbatsov/ruby-style-guide#self-assignment Enabled: false Style/SingleLineBlockParams: Description: Enforces the names of some block params. StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks Enabled: false Style/SingleLineMethods: Description: Avoid single-line methods. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods Enabled: false Style/SignalException: Description: Checks for proper usage of fail and raise. StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method Enabled: false Style/SpecialGlobalVars: Description: Avoid Perl-style global variables. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms Enabled: false Style/StringLiterals: Description: Checks if uses of quotes match the configured preference. StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals EnforcedStyle: single_quotes Enabled: true Style/TrailingCommaInArguments: Description: Checks for trailing comma in argument lists. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas EnforcedStyleForMultiline: no_comma SupportedStylesForMultiline: - comma - consistent_comma - no_comma Enabled: true Style/TrailingCommaInArrayLiteral: Description: Checks for trailing comma in array literals. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas EnforcedStyleForMultiline: comma SupportedStylesForMultiline: - comma - consistent_comma - no_comma Enabled: true Style/TrailingCommaInHashLiteral: Description: Checks for trailing comma in hash literals. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas EnforcedStyleForMultiline: comma SupportedStylesForMultiline: - comma - consistent_comma - no_comma Enabled: true Style/TrivialAccessors: Description: Prefer attr_* methods to trivial readers/writers. StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr_family Enabled: false Style/VariableInterpolation: Description: Don't interpolate global, instance and class variables directly in strings. StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate Enabled: false Style/WhenThen: Description: Use when x then ... for one-line cases. StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases Enabled: false Style/WhileUntilModifier: Description: Favor modifier while/until usage when you have a single-line body. StyleGuide: https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier Enabled: false Style/WordArray: Description: Use %w or %W for arrays of words. StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-w Enabled: false Layout/ConditionPosition: Description: Checks for condition placed in a confusing position relative to the keyword. StyleGuide: https://github.com/bbatsov/ruby-style-guide#same-line-condition Enabled: false Layout/DotPosition: Description: Checks the position of the dot in multi-line method calls. StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains EnforcedStyle: trailing Layout/ExtraSpacing: Description: Do not use unnecessary spacing. Enabled: true Layout/MultilineOperationIndentation: Description: Checks indentation of binary operations that span more than one line. Enabled: true EnforcedStyle: indented Layout/MultilineMethodCallIndentation: Description: Checks indentation of method calls with the dot operator that span more than one line. Enabled: true EnforcedStyle: indented Layout/InitialIndentation: Description: Checks the indentation of the first non-blank non-comment line in a file. Enabled: false Lint/AmbiguousOperator: Description: Checks for ambiguous operators in the first argument of a method invocation without parentheses. StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-as-args Enabled: false Lint/AmbiguousRegexpLiteral: Description: Checks for ambiguous regexp literals in the first argument of a method invocation without parenthesis. Enabled: false Lint/AssignmentInCondition: Description: Don't use assignment in conditions. StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition Enabled: false Lint/CircularArgumentReference: Description: Don't refer to the keyword argument in the default value. Enabled: false Lint/DeprecatedClassMethods: Description: Check for deprecated class method calls. Enabled: false Lint/EachWithObjectArgument: Description: Check for immutable argument given to each_with_object. Enabled: false Lint/ElseLayout: Description: Check for odd code arrangement in an else block. Enabled: false Lint/FormatParameterMismatch: Description: The number of parameters to format/sprint must match the fields. Enabled: false Lint/LiteralAsCondition: Description: Checks of literals used in conditions. Enabled: false Lint/LiteralInInterpolation: Description: Checks for literals used in interpolation. Enabled: false Lint/Loop: Description: Use Kernel#loop with break rather than begin/end/until or begin/end/while for post-loop tests. StyleGuide: https://github.com/bbatsov/ruby-style-guide#loop-with-break Enabled: false Lint/NestedMethodDefinition: Description: Do not use nested method definitions. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-methods Enabled: false Lint/NonLocalExitFromIterator: Description: Do not use return in iterator to cause non-local exit. Enabled: false Lint/ParenthesesAsGroupedExpression: Description: Checks for method calls with a space before the opening parenthesis. StyleGuide: https://github.com/bbatsov/ruby-style-guide#parens-no-spaces Enabled: false Lint/RequireParentheses: Description: Use parentheses in the method call to avoid confusion about precedence. Enabled: false Lint/UnderscorePrefixedVariableName: Description: Do not use prefix `_` for a variable that is used. Enabled: false Lint/Void: Description: Possible use of operator/literal/variable in void context. Enabled: false Performance/CaseWhenSplat: Description: Place `when` conditions that use splat at the end of the list of `when` branches. Enabled: false Performance/Count: Description: Use `count` instead of `select...size`, `reject...size`, `select...count`, `reject...count`, `select...length`, and `reject...length`. Enabled: false Performance/Detect: Description: Use `detect` instead of `select.first`, `find_all.first`, `select.last`, and `find_all.last`. Reference: https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code Enabled: false Performance/FlatMap: Description: Use `Enumerable#flat_map` instead of `Enumerable#map...Array#flatten(1)` or `Enumberable#collect..Array#flatten(1)` Reference: https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code Enabled: false Performance/ReverseEach: Description: Use `reverse_each` instead of `reverse.each`. Reference: https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code Enabled: false Performance/Size: Description: Use `size` instead of `count` for counting the number of elements in `Array` and `Hash`. Reference: https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code Enabled: false Performance/StringReplacement: Description: Use `tr` instead of `gsub` when you are replacing the same number of characters. Use `delete` instead of `gsub` when you are deleting characters. Reference: https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code Enabled: false Rails/ActionFilter: Description: Enforces consistent use of action filter methods. Enabled: false Rails/Date: Description: Checks the correct usage of date aware methods, such as Date.today, Date.current etc. Enabled: false Rails/FindBy: Description: Prefer find_by over where.first. Enabled: false Rails/FindEach: Description: Prefer all.find_each over all.find. Enabled: false Rails/HasAndBelongsToMany: Description: Prefer has_many :through to has_and_belongs_to_many. Enabled: false Rails/Output: Description: Checks for calls to puts, print, etc. Enabled: false Rails/ReadWriteAttribute: Description: Checks for read_attribute(:attr) and write_attribute(:attr, val). Enabled: false Rails/ScopeArgs: Description: Checks the arguments of ActiveRecord scopes. Enabled: false Rails/TimeZone: Description: Checks the correct usage of time zone aware methods. StyleGuide: https://github.com/bbatsov/rails-style-guide#time Reference: http://danilenko.org/2012/7/6/rails_timezones Enabled: false Rails/Validation: Description: Use validates :attribute, hash of validations. Enabled: false Lint/FlipFlop: Description: Checks for flip flops StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-flip-flops Enabled: false Layout/LineLength: Description: Limit lines to 80 characters. StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits Max: 100 Style/Sample: Description: Use `sample` instead of `shuffle.first`, `shuffle.last`, and `shuffle[Fixnum]`. Reference: https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code Enabled: false Layout/ParameterAlignment: Description: Here we check if the parameters on a multi-line method call or definition are aligned. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-double-indent Enabled: false Lint/DuplicateHashKey: Description: Check for duplicate keys in hash literals. Enabled: false Lint/SuppressedException: Description: Don't suppress exception. StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions Enabled: false Lint/RedundantCopDisableDirective: Description: 'Checks for rubocop:disable comments that can be removed. Note: this cop is not disabled when disabling all cops. It must be explicitly disabled.' Enabled: false ================================================ FILE: .ruby-version ================================================ ruby-3.1.2 ================================================ FILE: .travis.yml ================================================ dist: xenial sudo: required language: ruby rvm: - ruby-2.4.4 services: - redis-server - mysql env: global: - GATE_SERVER_URL=gate.server/url - SIGN_IN_TYPE= - GATE_OAUTH_CLIENT_ID=totally_secret_client_id - GATE_OAUTH_CLIENT_SECRET=totally_secret - GATE_HOSTED_DOMAIN=gate.domain - GATE_HOSTED_DOMAINS=test1.com,test2.com - GATE_DB_HOST=localhost - GATE_DB_PORT=3306 - GATE_DB_NAME= - GATE_DB_USER=root - GATE_DB_PASSWORD= - CACHE_HOST=localhost - CACHE_PORT=6379 - SECRET_KEY_BASE= - SECRET_API_KEY= - GATE_CONFIG_SECRET=gate_pw_secret - USER_ROLES=employee,consultant - UID_BUFFER=5000 - DEFAULT_HOST_PATTERN=s* - PRODUCT_LIST=pr1,pr2 - SAML_APPS=datadog - secure: Q2/gUeT/Y6IUKsRzgiiE6HGxLcHAzTtVCv6+6TcQnpJL9+HEO06+ISwekNXHRe/2BNMiWzDG5ufqo2YpA1j9h8899KAJZRYDULbbV0o8nrpT2kZSac1ZejBVYb/SrhDk8wrnQxQ061o6SdKpFEEwbIiJDzaqeQkv4tkyw3dEOq6+FRGS/QXj3mPIIArPbNCOtwcvnmg0mGIuPAAxuRZmz4Tuk2F6JdR4LrZstf33mvUYqbTw02Kl7UoErFjGSx2Ti+JPYIVDJ6D8pNCjv5va3awJskcXE5bj0evR5+OiP69GK6T2/UbP+QKwN2J36Pb9RxBKGuEkBTNuduch+h+RMiYk0A8MDO3Sy47i2v8Oj1xTEYqsNm0scmD2utZ93/VqPipYTrxuos7gbC4mdiW3dcMzyrhgaULEJlvalKaSQV41Xex8h/V7NCRvpoYjnyuWVUMQFgGSnE25Xpt1LvP3epcxlEBA9LcndskbEHaWup8mCD2bp+2Zq10iWi/hNnR2IBrj61fApTWBSoNplJMsMZNb3bverQVFFG79Gfr320HWk+eJVahZNFwUYp9VYn17r38nMRdfP9B85hm2ZUEiokzOvEXEKoSa2QVlCNQHk4HGJmfykPn4L8kDnS6LhYV3Yxugxwsl//1iUOwv4+ARMGn/rs13BDOixWXc+GVZIvk= - secure: hTSsv54GMEfKuZfk5UL3cekNK4ZgEEWwTp2cbfN39ViWBbq4xtXok1jREx4QyhwkNak47ewHuY0rifxPEYkJVJfj+tA/xiGs9BOG73gNEtQYhXXN/ZodXF0Kq3pYOfcVWDDo7oMAUoohkohHK2TA+KY3mfk5sFbxbXHOlNdSn0vXDSa8MgzALoN6+w1oWkC41perODfNS4EYD3gdhwDg4x6QHrmakrt0oOP4PPMCpOWinvkzWq7lBDdhkPSze47BwGilyUDFSJnunSKip88s6DlP4gNj6YYBiT6xGTPLuR76tgEPma9rp25zGvLxH9LsluMXTNuN0L3/6FzVyv0kJgXIwQrpgreQ+B45q3mT6iNom0alyNk3fYDKU59azpH9G0rC0zflmOhW+jssgwyJN08rJN8rFkhpQreD4dgey7+UDjf7qF3rOd0kXmqBvA/3z/aF8Mla/2+EIb1DjyHwnjAcPF2E0/97Gv2eGpZR/PPrQIVN2m8vEe3ce/39c4S/l9OU0G4Dqz9HMqIBG+0qFdS24+egbjaIoOQzO/eJwG/eew4ncvaz/ES8qCDtvFNeKR+QnfxcbJysybrxDNTqt3cBGTeotIdZxIB3rYK63+s+dvzggm09vBMafoP7zyWzD0xjSES7m+Gv9V8Jlvc7h5JSWrkmAgLmgnqt7FpI7hs= - secure: KtmItMzRHfZEPS54hTnYXNAWsqxCZZMtmxamJPcr2SVP+pzM/25eEF3M2b0JZECFRtW3xPBGita+4BjH8IY4NljH0IDVhPPKQsMHaVKtaqEeiTCRXCqkJd0DMUDeqyRcccwPDJSy9HeZns6E+zQDCGaC/tmFwDUWy5BYpairWd7sdw436iOeTmQ2fJ8SQ9lBr9LGpk8NwDmkYqg14fnOraIhw70tiWv9Wn82mEv+OwY4iKphOv2ZXEBeJYitoT1LQmhnaVvNvax16TYIY3Mxh89Oy2gVkKCgvbR9q1NmAIiWmMTyCLWxF0bd6A6Q+dHDDaQZF0pDsQqe83rnoA0xuaJUF9dh+ahvW0bn0aVFIa8tWgiBEMXWxHEDuEyfCWFis9vf3uj1xJl0SV0ktE5oIRzaF/0cTQorAwRy0UpN1HxK1yB9aITCad/BJ9ERK8MXYnfzC65jn2zHuQMjBz9Aeig4sgLc4IgtGM2DzabsIkhYG3ygL1TYaXXSj5Mo2KDwOuaZ46C7B6qxlzcesPmSnM3KF1CBatRZQK5tjsMm5GnxXKKtqg/udGtOA2fELlNolFsojtpmO4A79TAnebRUpEsU2vP0da1x7yTD5+2NWg6dnvq7tA3c5b8qBK2cAMnCHS0gmjaN3RYeFhLYO55dHDdmx/DNyQQ1V/YEkuisy80= - COMMIT=${TRAVIS_COMMIT::8} - DOCKER_COMPOSE_VERSION=1.11.2 before_install: - gem update --system - gem install bundler stages: - build, test and releases - name: build and publish docker if: (branch = master OR tag IS present) and type != pull_request jobs: include: - stage: build, test and releases script: - sudo mysql_upgrade -u root - sudo service mysql restart - mysql -e 'CREATE DATABASE IF NOT EXISTS gate_test;' - gem install bundler - bundle package --all - bundle install --binstubs --local - bundle exec rake db:migrate - bundle exec rake spec - bundle exec rake assets:precompile RAILS_ENV=production before_deploy: - "rm -rf $TRAVIS_BUILD_DIR/*.tar.gz" - "tar zcf gate-$TRAVIS_TAG.tar.gz -C $TRAVIS_BUILD_DIR ./*" deploy: provider: releases skip_cleanup: true api_key: secure: VWDGnrtrECoeJBATfe71HdpvnD15IKTkHWq/cQDfo9FTrm005ngTusQpW0eim76tSTMcJ1OBLoE9aAkd4Szi00TWenNPaAWHtgodcvNXxjqyZEtznRmtZW/vSk2H+iwJxJZoPDMZqStjmmSoD0dA1t2GLGltFzeJ5Y409YeroLUsVdRcTJY0mvQYbuLt1aZCu76gY5nB01yH7ib4ykZllJQbkyX57TKBYSLoQVUQ3hqo9BLUoROknHSfFGBHjSN9D3zIqKoh0XDOuTnyMHB+zMGmjjJbHMx5Z2eW1NW1HvpqDUOPl8uRnUEoCCfujA7OUlGgdadRBeUJObzpNkpQUNLEh5zIIr9NAZzjunzYkMiMZIseTNflJ6HOajld2Bcgz3PcbzM8hcIFog5loE7CBLWEzDoOIY/B75NYuHPIzRzTbgw7jf8XdfypCNMm0RLWiCXiDmuDMcNPZT9NrrheNGFz1CDmWkwpJmUTIv8J13J2Ux9ex1NY9iSRD8rgJ+bLOfp/u1nJWjACki8zq2/vffm2oUWz/R7q23Keq5Itddpe3mJUyFjokTUZPNSAlC45rF+rgK4ka7yJb+ZZAOaq/N8iIfVkgnfviWtaPQzAWRkO8J8Wzt1GrfQpNlo2BMM6Cpy0iV9ANYqAVYerImAopMWlzQmjbtt8P+xmmYA0FIw= file_glob: true file: $TRAVIS_BUILD_DIR/*.tar.gz on: tags: true repo: gate-sso/gate - stage: build and publish docker install: skip script: - gem install bundler - bundle install - bundle exec rake assets:precompile RAILS_ENV=production - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - docker build -t gatesso/gate:latest . - if [ -n "$TRAVIS_TAG" ]; then docker tag gatesso/gate:latest gatesso/gate:$TRAVIS_TAG; fi - docker images - docker push gatesso/gate ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.1.8] - 2020-02-17 - Securing organisations configuration page ## [1.1.7] - 2020-02-24 ### Fixed - Fix audit trail when user removed from group ## [1.1.6] - 2020-01-14 ### Changed - Store session to db using active record instead of using cookies ## [1.1.5] - 2019-10-20 ### Changed - User API now also return `id` ## [1.1.4] - 2019-10-05 ### Added - API to add user to group (POST /api/v1/groups/:group_id/users) ## [1.1.3] - 2019-09-17 ### Changed - Update devise to version `4.7.1`. ## [1.1.2] - 2019-09-17 ### Added - Show email of group admin on group detail - Show users join date to group on group detail ## [1.1.1] - 2019-09-02 ### Changed - Update nokogiri to version `1.10.4`. ## [1.1.0] - 2019-09-02 ### Fixed - Ensure that 'deactivated_at' on user is automatically set when we make user inactive ### Changed - Improve access policy for actions on resources including profile, user, api resource, host machine, organisation ### Added - Add endpoint entity to represent gate endpoint. Group will own endpoints, this mechanism will be used as an authorization for gate. - Add api to deactivate user in gate (PATCH /api/v1/users/:id/deactivate). ## [1.0.5] - 2019-08-06 ### Fixed - Improve loading time when opening group and user show page ## [1.0.4] - 2019-07-17 ### Fixed - If a user don't have any VPNs, they should still be able to click download VPN without incurring exception - Create missing tests for user model - Optimize queries when fetching sysadmins ## [1.0.3] - 2019-07-16 ### Fixed - Fix nil pointer exception when group members response is nil ## [1.0.2] - 2019-07-15 ### Fixed - Optimize slow queries on vpn model ## [1.0.1] - 2019-07-15 ### Added - Add the ability to only fetch active user for `/api/v1/users/profile` API ## [1.0.0] - 2019-07-15 ### Changed - Use dotenv instead of figaro. This is a breaking change and warrant a major version release. - All spiders are banned by default now in `robots.txt` - When admin account become inactive, the admin status will automatically revoked. - Admin can set expiration date on group assignment. This expiration date is optional, when not specified then it's a permanent assignment. ## [0.1.0] - 2019-05-20 ### Changed - Gate now uses semantic versioning. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines ## Releasing Gate > Gate uses semantic versioning, check [this page](https://semver.org/) for more details on how to bump the version number. Steps on releasing Gate. 1. Bump version on [VERSION](VERSION) file. 2. Add appropriate changelogs on [CHANGELOG.md](CHANGELOG.md) file. Please follow existing format. 3. Tag the commit by the new version number and push it, travis will automatically build and release Gate. ================================================ FILE: Dockerfile ================================================ FROM ruby:2.4 RUN apt-get update RUN curl -sL https://deb.nodesource.com/setup_10.x | bash RUN apt-get update -qq && apt-get install -y build-essential nodejs git RUN mkdir /app WORKDIR /app COPY Gemfile /app COPY Gemfile.lock /app RUN gem install bundler -v '>= 2.0' RUN bundle install --without development COPY . /app CMD [ "bundle", "exec", "rails", "s" ] ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' gem 'rails', '7.1.3.2' gem 'activerecord-session_store' gem 'ansi' gem 'bootstrap' gem 'countries', require: 'countries/global' gem 'devise' gem 'dotenv-rails' gem 'font-awesome-rails' gem 'httparty' gem 'jbuilder' gem 'mysql2' gem 'omniauth' gem 'omniauth-google-oauth2' gem 'paranoia' gem 'puma' gem 'redis' gem 'rotp' gem 'rqrcode' gem 'sassc-rails' #Disabling SAML Feature during upgrade # #TODO ENable this feature later #gem 'ruby-saml', '1.8.0' #gem 'saml_idp', git: 'https://github.com/gate-sso/saml_idp' gem 'sdoc', group: :doc # #TODO ENable this feature later gem 'slim-rails' gem 'turbolinks' gem 'uglifier' gem 'whenever', require: false group :development, :test do gem 'capybara' gem 'coveralls' gem 'database_cleaner' gem 'dredd_hooks' gem 'factory_bot_rails' gem 'faker' gem 'mock_redis' gem 'pry' gem 'rails-controller-testing' gem 'rspec-rails' gem 'rubocop' gem 'rubocop-faker' gem 'rubocop-performance' gem 'rubocop-rails' gem 'rubocop-rspec' gem 'shoulda-matchers' gem 'simplecov' gem 'simplecov-console' gem 'timecop' gem 'webmock' end group :development do gem 'web-console', platform: :ruby end ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 gate-sso Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Gate is now Gate-WireGuard ## DEPRECATED #### Please use [Gate-WireGuard](https://github.com/gate-sso/gate-wireguard) GateWireguard is based on WireGuard VPN Backend, also, it provides better security, faster speeds; it's much easier to install and run. ## *MASTER is broken* we are migrating to Rails 7. ## Ubuntu dependencies Apart from devtools you need to install the following packages as well. ``` apt-get install libmysqlclient-dev apt-get install libyaml-dev ``` #### Please note that we are upgrading gate RAILS version, and it will have breaking changes New RAILS 7 version will not be backward compatible and will not have many features We are removing many features, just to support API TOKENS and VPN functionality #### Please use [RAILS 5 Branch](https://github.com/gate-sso/gate/tree/RAILS_5_RELEASE) for backward compatibilty > Gate now uses semantic versioning to add more visibility on breaking changes. For users, you might want to check [CHANGELOG.md](CHANGELOG.md). For contributors, check [CONTRIBUTING.md](CONTRIBUTING.md). Gate is a single sign-on (SSO) platform for centralised authentication across Linux, OpenVPN and CAS. Gate works by automating OpenVPN profile creation for you and also providing you with google multi-factor authentication (MFA) integration. Gate provides single MFA Token authorisation across your organisation. Following scenarios can be handled by Gate: 1. Setup OpenVPN with Gate authentication. 2. Automatically create VPN profiles for each users. 3. Provide you with JaSig CAS Custom Authentication Handler to authenticate with Gate SSO and in turn enabling MFA for JaSig CAS. 4. Enable Linux authentication with [pam_gate](https://github.com/gate-sso/pam_gate), which sits like a small module with Linux and allow authentication. 5. Enable Name Service Switch (NSS) on Linux, so that Gate users can be discovered and authenticated on Linux. 6. **Access Control on Linux** Gate also allows you to control access to specific machines, like which hosts a user can login. And that can be controlled by reg-ex pattern on host name or IP addresses. (Note: pattern * matches everything). > The entry point for self sign-in is Google mail authentication. If you don't use Google mail authentication, you can point gate to any OAuth provider and it should work. > Gate provides you with single sign-on solution plus centralised user management across your applications and services. Not only it helps in controlling users access but it also helps in making most of it automated. ### Modules * [pam_gate](https://github.com/gate-sso/pam_gate) - Gate module for Linux PAM * [nss_gate](https://github.com/gate-sso/nss_gate) - Gate module for Linux Name Server Switch (NSS) * [cas_gate](https://github.com/gate-sso/cas_gate) - CAS Customer MFA authentication handler for Gate * open_vpn_gate - for OpenVPN setup, it is not extracted yet. ## Development Setup > We are in the process of improving Gate setup process, please check back for updated instructions. ### Manual Setup #### Initializing Your Application * Ensure that ruby is installed (>= 2.4) and `bundler` gem is installed. * Clone [Gate repository](https://github.com/gate-sso/gate) * Run `bundle install` * Run `rake app:init` to create environment file based on sample (we use dotenv to manage environment variables). #### Setting up OAuth (Optional) > If you setup Gate for development purpose and you want to avoid setting up OAuth, you can fill `SIGN_IN_TYPE` environment variable with `form`. This option will provide you with sign-in form in Gate homepage that you can fill with e-mail and name to sign-in. > Note that you still need to update `GATE_HOSTED_DOMAINS` to serve your e-mail domain. Check [this guide](docs/oauth_setup.md) For detailed information on how to setup OAuth. #### Setting up Database and Cache * Install and setup database (mysql) and update the following values (`GATE_DB_HOST`, `GATE_DB_PORT`, `GATE_DB_USER`, `GATE_DB_PASSWORD`) on `.env`. * Install and setup cache (redis) and update the following values (`CACHE_DB`, `CACHE_HOST`). #### Finishing Steps To finalize your setup you just need to run `rake app:setup`. This command will setup your database and also run inital set of tests to make sure you have a successful setup. Once Gate is setup, sign-in with your user and you should see welcome page with VPN profile download and VPN MFA Scanning. If you want Gate to setup VPN for you then just install OpenVPN with easy rsa. Gate should just work fine with it. > **NOTE** We will be putting more effort to automate VPN setup using Gate as well. Or you can send a pull request to help us with this. ### Docker Setup * Build docker image using `docker build -t gate .` * Create and update `.env` file according to `.env.example` with appropriate values * Run the image using `docker run -p 3000:3000 --env-file=.env -it gate` * If you want use docker-compose run using `docker-compose up` ## Additional Topics * [API Blueprint Test](docs/dredd_setup.md) * [Additional Setup](docs/additional_setup.md) * [Administration](docs/administration.md) * [Newrelic Integration](docs/newrelic.md) ### Changelog See [CHANGELOG.md](CHANGELOG.md) ### Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) ### License MIT License, See [LICENSE](LICENSE). ================================================ FILE: Rakefile ================================================ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) Rails.application.load_tasks ================================================ FILE: VERSION ================================================ 1.1.8 ================================================ FILE: api_blueprint/bin/dredd_server.sh ================================================ #!/bin/bash # dredd_server.sh kill -9 $(lsof -i tcp:9865 -t) export RAILS_ENV=test export LOG_LEVEL=info rake db:drop rake db:setup bundle exec rails server --port=9865 ================================================ FILE: api_blueprint/group.apib ================================================ FORMAT: 1A # API Group # Group [/api/v1/groups] ## Create Groups [POST] Create new group + Request(application/json) + Body { "access_token": "token", "name" : "foo" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "access_token" : { "type" : "string" } } } + Response 200 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: application/json; charset=utf-8 etag: W/"5c15461069e69109955c72671ffc465d" cache-control: max-age=0, private, must-revalidate x-request-id: 0613ec11-afd7-4dc0-9096-fee607d71c12 x-runtime: 0.073641 connection: close transfer-encoding: chunked + Body { "id": 1, "name": "foo" } + Schema { "type": "object", "properties" : { "id" : { "type" : "int" }, "name" : { "type" : "string" } } } + Request(application/json) + Body { "access_token": "token", "name" : "foo" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "access_token" : { "type" : "string" } } } + Response 422 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: application/json; charset=utf-8 cache-control: no-cache x-request-id: 30ec7a3a-4794-435b-a5b9-8ff480cd648c x-runtime: 0.007976 connection: close transfer-encoding: chunked + Body { "status": "group already exist", "id": 1, "name": "foo" } + Schema { "type": "object", "properties" : { "status" : { "type" : "string" }, "id" : { "type" : "int" }, "name" : { "type" : "string" } } } + Request(application/json) + Body { "access_token": "wrong token", "name" : "foo" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "access_token" : { "type" : "string" } } } + Response 401 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: text/html cache-control: no-cache x-request-id: 30276bf6-0ce6-4ba6-b87d-7528b4f2c85d x-runtime: 0.002650 connection: close transfer-encoding: chunked + Body # Group Users [/api/v1/groups/{group_id}/users] + Parameters + group_id: 1 (required, number) - ID of the Group in form of an integer ## Add user [POST] + user_id (required, number) - ID of the User in form of an integer + expiration_date (optional, date) - Membership expiration date in format (YYYY-MM-DD) + Request (application/json) + Header Authorization: + Body { "user_id": 7, "expiration_date": "2019-11-20" } + Response 204 ================================================ FILE: api_blueprint/hooks/dredd_hooks.rb ================================================ ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __dir__) require 'dredd_hooks/methods' include DreddHooks::Methods before_all do |_| user = User.create(name: 'foo', email: 'bar@test.com', admin: 1) access_token = AccessToken.new access_token.token = 'token' access_token.user = user user.access_token = access_token access_token.save! user.save! inactive_user = User.create(name: 'foo', email: 'foo@test2.com', active: 0, user_login_id: 'foo') inactive_user.save! active_user = User.create(name: 'foo', email: 'foo@test.com', active: 1, user_login_id: 'foo') active_user.save! end ================================================ FILE: api_blueprint/user.apib ================================================ FORMAT: 1A # API User # User [/api/v1/users] ## Create Users [POST] Create new users gate + Request(application/json) + Body { "access_token": "token", "name" : "foo", "email" : "foo@test.com" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "email" : { "type" : "string" }, "access_token" : { "type" : "string" } } } + Response 200 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: application/json; charset=utf-8 etag: W/"5c15461069e69109955c72671ffc465d" cache-control: max-age=0, private, must-revalidate x-request-id: 0613ec11-afd7-4dc0-9096-fee607d71c12 x-runtime: 0.073641 connection: close transfer-encoding: chunked + Body { "status" : "created" } + Schema { "type": "object", "properties" : { "status" : { "type" : "string" } } } + Request(application/json) + Body { "access_token": "wrong token", "name" : "foo", "email" : "foo@test.com" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "email" : { "type" : "string" }, "access_token" : { "type" : "string" } } } + Response 401 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: text/html cache-control: no-cache x-request-id: 30276bf6-0ce6-4ba6-b87d-7528b4f2c85d x-runtime: 0.002650 connection: close transfer-encoding: chunked + Body # User [/api/v1/users/profile/{?email,uid,username,active}] ## Get Users [GET] Get user data with email, uid or username + Request + Headers Authorization: token + Parameters + email (string, optional) + Default: bar@test.com + uid (string, optional) + username (string, optional) + active ((string, boolean, int), optional) + Response 200 + Body { "email": "bar@test.com", "id": null, "uid": null, "name": "foo", "active": true, "admin": true, "home_dir": null, "shell": null, "public_key": null, "user_login_id": null, "product_name": null, "groups": [] } + Request + Headers Authorization: token + Parameters + email (string, optional) + uid (string, optional) + username (string, optional) + Default: foo + active ((string, boolean, int), optional) + Default: 1 + Response 200 + Body { "email": "foo@test.com", "uid": null, "name": "foo", "active": true, "admin": true, "home_dir": null, "shell": null, "public_key": null, "user_login_id": "foo", "product_name": null, "groups": [] } + Request + Headers Authorization: token + Parameters + email (string, optional) + Default: notfound@test.com + uid (string, optional) + username (string, optional) + active ((string, boolean, int), optional) + Response 404 + Body # User [/api/v1/users/profile] ## Update Users [POST] Update users data with email as key + Request(application/json) + Headers Authorization: token + Body { "name" : "foo bar", "email" : "bar@test.com" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "email" : { "type" : "string" } } } + Response 200 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: application/json; charset=utf-8 etag: W/"c955e57777ec0d73639dca6748560d00" cache-control: max-age=0, private, must-revalidate x-request-id: 136874b1-39fb-4704-b548-d7670773a4bf x-runtime: 0.014707 connection: close transfer-encoding: chunked + Body { "success": true } + Request(application/json) + Headers Authorization: wrong-token + Body { "name" : "foo bar", "email" : "bar@test.com" } + Schema { "type": "object", "properties" : { "name" : { "type" : "string" }, "email" : { "type" : "string" } } } + Response 401 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: text/html cache-control: no-cache x-request-id: 85cb1679-58d4-41c4-9479-ce5f9c75ca7a x-runtime: 0.002542 connection: close transfer-encoding: chunked ================================================ FILE: api_blueprint/vpns.apib ================================================ FORMAT: 1A # API VPNS # VPNS [/api/v1/vpns] ## Create VPNS [POST] Create new vpns + Request(application/json) + Headers Authorization: token + Body { "id": 1, "name": "test-vpn", "host_name": "test-vpn.example.com", "ip_address" : "10.10.10.10" } + Schema { "type": "object", "properties" : { "id" : { "type" : int }, "name" : { "type" : "string" }, "host_name" : { "type" : "string" }, "ip_address" : { "type" : "string" } } } + Response 200 + Body { "id": 1, "name": "test-vpn", "host_name": "test-vpn.example.com", "ip_address": "10.10.10.10" } + Request(application/json) + Headers Authorization: wrong-token + Body + Response 401 + Headers x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: text/html cache-control: no-cache x-request-id: 73b10aa8-6f68-400b-b723-2e992ac4d000 x-runtime: 0.002242 connection: close transfer-encoding: chunked ================================================ FILE: app/assets/config/manifest.js ================================================ //= link application.css //= link apple-touch-icon-144x144-precomposed.png //= link apple-touch-icon-72x72-precomposed.png //= link apple-touch-icon-precomposed.png //= link favicon.ico //= link application.js ================================================ FILE: app/assets/images/.keep ================================================ ================================================ FILE: app/assets/javascripts/admin.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/api_resources.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ API_RESOURCE_NAME_FORMAT_ERROR_MSG = "Please enter valid name containing only alphanumeric (a-z, A-Z, 0-9), underscore (_) and dash (-)" API_RESOURCE_NAME_UNIQUENESS_ERROR_MSG = "API name already taken" API_RESOURCE_DESC_REQUIRED_ERROR_MSG = "Please enter description" api_resources_ready = -> $('#api_resource_name').on 'blur', -> valid = validate_pattern($('#api_resource_name'), '^[a-zA-Z0-9_-]+$', API_RESOURCE_NAME_FORMAT_ERROR_MSG) if valid validate_uniqueness($('#api_resource_name'), '/api_resources/search', API_RESOURCE_NAME_UNIQUENESS_ERROR_MSG) $('#api_resource_description').on 'blur', -> validate_required($('#api_resource_description'), API_RESOURCE_DESC_REQUIRED_ERROR_MSG) $('#new_api_resource').submit (event) -> if $('.is-invalid').length > 0 event.preventDefault() event.stopPropagation() return $(document).on('turbolinks:load', api_resources_ready) ================================================ FILE: app/assets/javascripts/application.js ================================================ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // // require jquery3 // require popper // require bootstrap-sprockets // require jquery_ujs // require turbolinks // require_tree . ================================================ FILE: app/assets/javascripts/bootstrap.js.coffee ================================================ jQuery -> $("a[rel~=popover], .has-popover").popover() $("a[rel~=tooltip], .has-tooltip").tooltip() ================================================ FILE: app/assets/javascripts/group.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ group_ready = -> $('#assign_admin_include_inactive_user').prop('checked', false); $('#assign_admin_user_id').selectize maxItems: 1 valueField: 'id' labelField: 'name' searchField: 'name_email' create: false render: option: (item, escape) -> '
' + escape(item.name) + '
' + escape(item.email) + '
' load: (query, callback) -> if !query.length return callback() # Clear the cache, because user list may change this.clearOptions() include_inactive = $('#assign_admin_include_inactive_user').is(':checked') # Use remote as source $.ajax url: '/users/search?q=' + encodeURIComponent(query) + '&include_inactive=' + include_inactive type: 'GET' error: -> callback() return success: (res) -> callback res return return $('#assign_admin_user_id').on 'change', -> set_allow_submit($(this).val(), $(this)) $('#add_user_include_inactive_user').prop('checked', false); $('#add_user_user_id').selectize maxItems: 1 valueField: 'id' labelField: 'name' searchField: 'name_email' create: false render: option: (item, escape) -> '
' + escape(item.name) + '
' + escape(item.email) + '
' load: (query, callback) -> if !query.length return callback() # Clear the cache, because user list may change this.clearOptions() include_inactive = $('#add_user_include_inactive_user').is(':checked') # Use remote as source $.ajax url: '/users/search?q=' + encodeURIComponent(query) + '&include_inactive=' + include_inactive type: 'GET' error: -> callback() return success: (res) -> callback res return return $('#add_user_user_id').on 'change', -> set_allow_submit($(this).val(), $(this)) $('#add_vpn_vpn_id').selectize maxItems: 1 valueField: 'id' labelField: 'name' searchField: 'name' create: false render: option: (item, escape) -> '
' + escape(item.name) + '
' load: (query, callback) -> if !query.length return callback() # Use remote as source $.ajax url: '/vpns/search?q=' + encodeURIComponent(query) type: 'GET' error: -> callback() return success: (res) -> callback res return return $('#add_vpn_vpn_id').on 'change', -> set_allow_submit($(this).val(), $(this)) $('#add_machine_machine_id').selectize maxItems: 1 valueField: 'id' labelField: 'name' searchField: 'name' create: false render: option: (item, escape) -> '
' + escape(item.name) + '
' load: (query, callback) -> if !query.length return callback() # Use remote as source $.ajax url: '/host_machines/search?q=' + encodeURIComponent(query) type: 'GET' error: -> callback() return success: (res) -> callback res return return $('#add_machine_machine_id').on 'change', -> set_allow_submit($(this).val(), $(this)) $(document).on('turbolinks:load', group_ready) ================================================ FILE: app/assets/javascripts/groups.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/home.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/host_access_groups.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/host_machine_groups.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/host_machines.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ host_machines_ready = -> $('#group_id').selectize maxItems: 1 valueField: 'id' labelField: 'name' searchField: 'name' create: false render: option: (item, escape) -> '
' + escape(item.name) + '
' load: (query, callback) -> if !query.length return callback() # Use remote as source $.ajax url: '/groups/search?q=' + encodeURIComponent(query) type: 'GET' error: -> callback() return success: (res) -> callback res return return $('#group_id').on 'change', -> set_allow_submit($(this).val(), $(this)) $(document).on('turbolinks:load', host_machines_ready) ================================================ FILE: app/assets/javascripts/nss.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/omniauth_callbacks.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/profile.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/users.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ users_ready = -> $('#group_id').selectize maxItems: 1 valueField: 'id' labelField: 'name' searchField: 'name' create: false render: option: (item, escape) -> '
' + escape(item.name) + '
' load: (query, callback) -> if !query.length return callback() # Use remote as source $.ajax url: '/groups/search?q=' + encodeURIComponent(query) type: 'GET' error: -> callback() return success: (res) -> callback res return return $('#group_id').on 'change', -> set_allow_submit($(this).val(), $(this)) $(document).on('turbolinks:load', users_ready) ================================================ FILE: app/assets/javascripts/utilities.coffee ================================================ # Utility functions @append_error_msg = (elem, res, msg) -> if !res elem.addClass('is-invalid') elem.next('.invalid-feedback').html(msg) else elem.removeClass('is-invalid') elem.next('.invalid-feedback').html('') @validate_required = (elem, msg) -> input = elem.val() append_error_msg(elem, input, msg) return !!input @validate_pattern = (elem, pattern, msg) -> input = elem.val() regex = new RegExp(pattern) validate_regex = regex.test(input) append_error_msg(elem, validate_regex, msg) return !!validate_regex @validate_uniqueness = (elem, check_url, msg) -> input = elem.val() $.ajax url: check_url + '?q=' + encodeURIComponent(input) + '&exact=true' type: 'GET' error: -> return success: (res) -> append_error_msg(elem, $.isEmptyObject(res), msg) return @set_allow_submit = (cond, elem) -> curSubmit = $("input[type=submit]", $(elem).parents('form')) if !!cond curSubmit.prop("disabled", false) else curSubmit.prop('disabled', true) return ================================================ FILE: app/assets/javascripts/viewport.js ================================================ /*! * IE10 viewport hack for Surface/desktop Windows 8 bug * Copyright 2014-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ // See the Getting Started docs for more information: // http://getbootstrap.com/getting-started/#support-ie10-width (function () { 'use strict'; if (navigator.userAgent.match(/IEMobile\/10\.0/)) { var msViewportStyle = document.createElement('style') msViewportStyle.appendChild( document.createTextNode( '@-ms-viewport{width:auto!important}' ) ) document.querySelector('head').appendChild(msViewportStyle) } })(); ================================================ FILE: app/assets/stylesheets/application.scss ================================================ /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require_tree . *= require_self *= stub 'profile' *= stub 'general' */ @import url(https://fonts.googleapis.com/css?family=Roboto:300); @import url(https://fonts.googleapis.com/css?family=Lato:300); @import "bootstrap"; @import "font-awesome"; /* * https://github.com/ladjs/bootstrap-social */ @import "bootstrap-social"; a.btn { color: white!important; } .form-group .field_with_errors { display: block; background: none; } .form-group .field_with_errors label::after { content: ' *'; color: red; } body { font-family: 'Roboto', 'Lato', sans-serif; } .container-profile { max-width: 768px; min-width: 768px; } a:visited { color: blue; } /* mouse over link */ a:hover { color: blue; background: none; } .table-responsive { overflow-x: inherit; } #configAppTabsContent .tab-pane { padding-top: 20px; } #configAppTabsContent ol li { margin-top: 10px; } #configAppTabsContent li > ol { margin-bottom: 10px; } ================================================ FILE: app/assets/stylesheets/bootstrap-social.scss ================================================ /* * Social Buttons for Bootstrap * * Copyright 2013-2016 Panayiotis Lipiridis * Licensed under the MIT License * * https://github.com/lipis/bootstrap-social */ .btn-social, .btn-social-icon { position: relative; padding-left: 3.5rem; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .btn-social > :first-child, .btn-social-icon > :first-child { position: absolute; left: 0; top: 0; bottom: 0; width: 3rem; line-height: 5rem; font-size: 1.6em; text-align: center; border-right: 1px solid rgba(0, 0, 0, 0.2); } .btn-social.btn-lg, .btn-lg.btn-social-icon { padding-left: 4.75rem; } .btn-social.btn-lg > :first-child, .btn-lg.btn-social-icon > :first-child { line-height: 4rem; width: 4rem; font-size: 1.8em; } .btn-social.btn-sm, .btn-sm.btn-social-icon { padding-left: 2.25rem; } .btn-social.btn-sm > :first-child, .btn-sm.btn-social-icon > :first-child { line-height: 2rem; width: 2rem; font-size: 1.4em; } .btn-social.btn-xs, .btn-xs.btn-social-icon { padding-left: 2.25rem; } .btn-social.btn-xs > :first-child, .btn-xs.btn-social-icon > :first-child { line-height: 2rem; width: 2rem; font-size: 1.2em; } .btn-social > :first-child, .btn-social-icon > :first-child { line-height: 1.25 !important; padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; font-size: inherit !important; } .btn-social.btn-lg > :first-child, .btn-lg.btn-social-icon > :first-child { line-height: 1.25 !important; padding-top: 0.75rem !important; padding-bottom: 0.75rem !important; font-size: inherit !important; } .btn-social-icon { height: 3rem; width: 3rem; padding: 0; } .btn-social-icon > :first-child { border: none; text-align: center; width: 100% !important; } .btn-social-icon.btn-lg { height: 4rem; width: 4rem; padding-left: 0; padding-right: 0; } .btn-social-icon.btn-sm { height: 2rem; width: 2rem; padding-left: 0; padding-right: 0; } .btn-social-icon.btn-xs { height: 2rem; width: 2rem; padding-left: 0; padding-right: 0; } .btn-adn { color: #fff; background-color: #d87a68; border-color: #d87a68; } .btn-adn:hover { color: #fff; background-color: #d05f4a; border-color: #ce563f; } .btn-adn:focus, .btn-adn.focus { box-shadow: 0 0 0 3px rgba(216, 122, 104, 0.5); } .btn-adn.disabled, .btn-adn:disabled { background-color: #d87a68; border-color: #d87a68; } .btn-adn:active, .btn-adn.active, .show > .btn-adn.dropdown-toggle { background-color: #d05f4a; background-image: none; border-color: #ce563f; } .btn-bitbucket { color: #fff; background-color: #205081; border-color: #205081; } .btn-bitbucket:hover { color: #fff; background-color: #183d62; border-color: #163758; } .btn-bitbucket:focus, .btn-bitbucket.focus { box-shadow: 0 0 0 3px rgba(32, 80, 129, 0.5); } .btn-bitbucket.disabled, .btn-bitbucket:disabled { background-color: #205081; border-color: #205081; } .btn-bitbucket:active, .btn-bitbucket.active, .show > .btn-bitbucket.dropdown-toggle { background-color: #183d62; background-image: none; border-color: #163758; } .btn-dropbox { color: #fff; background-color: #1087dd; border-color: #1087dd; } .btn-dropbox:hover { color: #fff; background-color: #0d71b9; border-color: #0d6aad; } .btn-dropbox:focus, .btn-dropbox.focus { box-shadow: 0 0 0 3px rgba(16, 135, 221, 0.5); } .btn-dropbox.disabled, .btn-dropbox:disabled { background-color: #1087dd; border-color: #1087dd; } .btn-dropbox:active, .btn-dropbox.active, .show > .btn-dropbox.dropdown-toggle { background-color: #0d71b9; background-image: none; border-color: #0d6aad; } .btn-facebook { color: #fff; background-color: #3b5998; border-color: #3b5998; } .btn-facebook:hover { color: #fff; background-color: #30497c; border-color: #2d4373; } .btn-facebook:focus, .btn-facebook.focus { box-shadow: 0 0 0 3px rgba(59, 89, 152, 0.5); } .btn-facebook.disabled, .btn-facebook:disabled { background-color: #3b5998; border-color: #3b5998; } .btn-facebook:active, .btn-facebook.active, .show > .btn-facebook.dropdown-toggle { background-color: #30497c; background-image: none; border-color: #2d4373; } .btn-flickr { color: #fff; background-color: #ff0084; border-color: #ff0084; } .btn-flickr:hover { color: #fff; background-color: #d90070; border-color: #cc006a; } .btn-flickr:focus, .btn-flickr.focus { box-shadow: 0 0 0 3px rgba(255, 0, 132, 0.5); } .btn-flickr.disabled, .btn-flickr:disabled { background-color: #ff0084; border-color: #ff0084; } .btn-flickr:active, .btn-flickr.active, .show > .btn-flickr.dropdown-toggle { background-color: #d90070; background-image: none; border-color: #cc006a; } .btn-foursquare { color: #fff; background-color: #f94877; border-color: #f94877; } .btn-foursquare:hover { color: #fff; background-color: #f8235b; border-color: #f71752; } .btn-foursquare:focus, .btn-foursquare.focus { box-shadow: 0 0 0 3px rgba(249, 72, 119, 0.5); } .btn-foursquare.disabled, .btn-foursquare:disabled { background-color: #f94877; border-color: #f94877; } .btn-foursquare:active, .btn-foursquare.active, .show > .btn-foursquare.dropdown-toggle { background-color: #f8235b; background-image: none; border-color: #f71752; } .btn-github { color: #fff; background-color: #444444; border-color: #444444; } .btn-github:hover { color: #fff; background-color: #313131; border-color: #2b2b2b; } .btn-github:focus, .btn-github.focus { box-shadow: 0 0 0 3px rgba(68, 68, 68, 0.5); } .btn-github.disabled, .btn-github:disabled { background-color: #444444; border-color: #444444; } .btn-github:active, .btn-github.active, .show > .btn-github.dropdown-toggle { background-color: #313131; background-image: none; border-color: #2b2b2b; } .btn-google { color: #fff; background-color: #dd4b39; border-color: #dd4b39; } .btn-google:hover { color: #fff; background-color: #cd3623; border-color: #c23321; } .btn-google:focus, .btn-google.focus { box-shadow: 0 0 0 3px rgba(221, 75, 57, 0.5); } .btn-google.disabled, .btn-google:disabled { background-color: #dd4b39; border-color: #dd4b39; } .btn-google:active, .btn-google.active, .show > .btn-google.dropdown-toggle { background-color: #cd3623; background-image: none; border-color: #c23321; } .btn-instagram { color: #fff; background-color: #3f729b; border-color: #3f729b; } .btn-instagram:hover { color: #fff; background-color: #345e80; border-color: #305777; } .btn-instagram:focus, .btn-instagram.focus { box-shadow: 0 0 0 3px rgba(63, 114, 155, 0.5); } .btn-instagram.disabled, .btn-instagram:disabled { background-color: #3f729b; border-color: #3f729b; } .btn-instagram:active, .btn-instagram.active, .show > .btn-instagram.dropdown-toggle { background-color: #345e80; background-image: none; border-color: #305777; } .btn-linkedin { color: #fff; background-color: #007bb6; border-color: #007bb6; } .btn-linkedin:hover { color: #fff; background-color: #006190; border-color: #005983; } .btn-linkedin:focus, .btn-linkedin.focus { box-shadow: 0 0 0 3px rgba(0, 123, 182, 0.5); } .btn-linkedin.disabled, .btn-linkedin:disabled { background-color: #007bb6; border-color: #007bb6; } .btn-linkedin:active, .btn-linkedin.active, .show > .btn-linkedin.dropdown-toggle { background-color: #006190; background-image: none; border-color: #005983; } .btn-microsoft { color: #fff; background-color: #2672ec; border-color: #2672ec; } .btn-microsoft:hover { color: #fff; background-color: #135fd9; border-color: #125acd; } .btn-microsoft:focus, .btn-microsoft.focus { box-shadow: 0 0 0 3px rgba(38, 114, 236, 0.5); } .btn-microsoft.disabled, .btn-microsoft:disabled { background-color: #2672ec; border-color: #2672ec; } .btn-microsoft:active, .btn-microsoft.active, .show > .btn-microsoft.dropdown-toggle { background-color: #135fd9; background-image: none; border-color: #125acd; } .btn-odnoklassniki { color: #fff; background-color: #f4731c; border-color: #f4731c; } .btn-odnoklassniki:hover { color: #fff; background-color: #df600b; border-color: #d35b0a; } .btn-odnoklassniki:focus, .btn-odnoklassniki.focus { box-shadow: 0 0 0 3px rgba(244, 115, 28, 0.5); } .btn-odnoklassniki.disabled, .btn-odnoklassniki:disabled { background-color: #f4731c; border-color: #f4731c; } .btn-odnoklassniki:active, .btn-odnoklassniki.active, .show > .btn-odnoklassniki.dropdown-toggle { background-color: #df600b; background-image: none; border-color: #d35b0a; } .btn-openid { color: #111; background-color: #f7931e; border-color: #f7931e; } .btn-openid:hover { color: #111; background-color: #e78008; border-color: #da7908; } .btn-openid:focus, .btn-openid.focus { box-shadow: 0 0 0 3px rgba(247, 147, 30, 0.5); } .btn-openid.disabled, .btn-openid:disabled { background-color: #f7931e; border-color: #f7931e; } .btn-openid:active, .btn-openid.active, .show > .btn-openid.dropdown-toggle { background-color: #e78008; background-image: none; border-color: #da7908; } .btn-pinterest { color: #fff; background-color: #cb2027; border-color: #cb2027; } .btn-pinterest:hover { color: #fff; background-color: #aa1b21; border-color: #9f191f; } .btn-pinterest:focus, .btn-pinterest.focus { box-shadow: 0 0 0 3px rgba(203, 32, 39, 0.5); } .btn-pinterest.disabled, .btn-pinterest:disabled { background-color: #cb2027; border-color: #cb2027; } .btn-pinterest:active, .btn-pinterest.active, .show > .btn-pinterest.dropdown-toggle { background-color: #aa1b21; background-image: none; border-color: #9f191f; } .btn-reddit { color: #111; background-color: #eff7ff; border-color: #eff7ff; } .btn-reddit:hover { color: #111; background-color: #c9e4ff; border-color: #bcdeff; } .btn-reddit:focus, .btn-reddit.focus { box-shadow: 0 0 0 3px rgba(239, 247, 255, 0.5); } .btn-reddit.disabled, .btn-reddit:disabled { background-color: #eff7ff; border-color: #eff7ff; } .btn-reddit:active, .btn-reddit.active, .show > .btn-reddit.dropdown-toggle { background-color: #c9e4ff; background-image: none; border-color: #bcdeff; } .btn-soundcloud { color: #fff; background-color: #ff5500; border-color: #ff5500; } .btn-soundcloud:hover { color: #fff; background-color: #d94800; border-color: #cc4400; } .btn-soundcloud:focus, .btn-soundcloud.focus { box-shadow: 0 0 0 3px rgba(255, 85, 0, 0.5); } .btn-soundcloud.disabled, .btn-soundcloud:disabled { background-color: #ff5500; border-color: #ff5500; } .btn-soundcloud:active, .btn-soundcloud.active, .show > .btn-soundcloud.dropdown-toggle { background-color: #d94800; background-image: none; border-color: #cc4400; } .btn-tumblr { color: #fff; background-color: #2c4762; border-color: #2c4762; } .btn-tumblr:hover { color: #fff; background-color: #203448; border-color: #1c2e3f; } .btn-tumblr:focus, .btn-tumblr.focus { box-shadow: 0 0 0 3px rgba(44, 71, 98, 0.5); } .btn-tumblr.disabled, .btn-tumblr:disabled { background-color: #2c4762; border-color: #2c4762; } .btn-tumblr:active, .btn-tumblr.active, .show > .btn-tumblr.dropdown-toggle { background-color: #203448; background-image: none; border-color: #1c2e3f; } .btn-twitter { color: #fff; color: #fff; background-color: #1DA1F2; border-color: #1DA1F2; } .btn-twitter:hover { color: #fff; background-color: #0d8ddc; border-color: #0c85d0; } .btn-twitter:focus, .btn-twitter.focus { box-shadow: 0 0 0 3px rgba(29, 161, 242, 0.5); } .btn-twitter.disabled, .btn-twitter:disabled { background-color: #1DA1F2; border-color: #1DA1F2; } .btn-twitter:active, .btn-twitter.active, .show > .btn-twitter.dropdown-toggle { background-color: #0d8ddc; background-image: none; border-color: #0c85d0; } .btn-vimeo { color: #fff; background-color: #1ab7ea; border-color: #1ab7ea; } .btn-vimeo:hover { color: #fff; background-color: #139ecb; border-color: #1295bf; } .btn-vimeo:focus, .btn-vimeo.focus { box-shadow: 0 0 0 3px rgba(26, 183, 234, 0.5); } .btn-vimeo.disabled, .btn-vimeo:disabled { background-color: #1ab7ea; border-color: #1ab7ea; } .btn-vimeo:active, .btn-vimeo.active, .show > .btn-vimeo.dropdown-toggle { background-color: #139ecb; background-image: none; border-color: #1295bf; } .btn-vk { color: #fff; background-color: #587ea3; border-color: #587ea3; } .btn-vk:hover { color: #fff; background-color: #4b6b8a; border-color: #466482; } .btn-vk:focus, .btn-vk.focus { box-shadow: 0 0 0 3px rgba(88, 126, 163, 0.5); } .btn-vk.disabled, .btn-vk:disabled { background-color: #587ea3; border-color: #587ea3; } .btn-vk:active, .btn-vk.active, .show > .btn-vk.dropdown-toggle { background-color: #4b6b8a; background-image: none; border-color: #466482; } .btn-yahoo { color: #fff; background-color: #720e9e; border-color: #720e9e; } .btn-yahoo:hover { color: #fff; background-color: #590b7b; border-color: #500a6f; } .btn-yahoo:focus, .btn-yahoo.focus { box-shadow: 0 0 0 3px rgba(114, 14, 158, 0.5); } .btn-yahoo.disabled, .btn-yahoo:disabled { background-color: #720e9e; border-color: #720e9e; } .btn-yahoo:active, .btn-yahoo.active, .show > .btn-yahoo.dropdown-toggle { background-color: #590b7b; background-image: none; border-color: #500a6f; } .btn-stripe { color: #fff; background-color: #1275FF; border-color: #1275FF; } .btn-stripe:hover { color: #fff; background-color: #0062eb; border-color: #005dde; } .btn-stripe:focus, .btn-stripe.focus { box-shadow: 0 0 0 3px rgba(18, 117, 255, 0.5); } .btn-stripe.disabled, .btn-stripe:disabled { background-color: #1275FF; border-color: #1275FF; } .btn-stripe:active, .btn-stripe.active, .show > .btn-stripe.dropdown-toggle { background-color: #0062eb; background-image: none; border-color: #005dde; } .btn-amazon { color: #232F3E; color: #111; background-color: #FF9900; border-color: #FF9900; } .btn-amazon:hover { color: #111; background-color: #d98200; border-color: #cc7a00; } .btn-amazon:focus, .btn-amazon.focus { box-shadow: 0 0 0 3px rgba(255, 153, 0, 0.5); } .btn-amazon.disabled, .btn-amazon:disabled { background-color: #FF9900; border-color: #FF9900; } .btn-amazon:active, .btn-amazon.active, .show > .btn-amazon.dropdown-toggle { background-color: #d98200; background-image: none; border-color: #cc7a00; } .btn-patreon { color: #052D49; color: #fff; background-color: #F96854; border-color: #F96854; } .btn-patreon:hover { color: #fff; background-color: #f8472f; border-color: #f73c23; } .btn-patreon:focus, .btn-patreon.focus { box-shadow: 0 0 0 3px rgba(249, 104, 84, 0.5); } .btn-patreon.disabled, .btn-patreon:disabled { background-color: #F96854; border-color: #F96854; } .btn-patreon:active, .btn-patreon.active, .show > .btn-patreon.dropdown-toggle { background-color: #f8472f; background-image: none; border-color: #f73c23; } .btn-untappd { color: #111; background-color: #ffc000; border-color: #ffc000; } .btn-untappd:hover { color: #111; background-color: #d9a300; border-color: #cc9a00; } .btn-untappd:focus, .btn-untappd.focus { box-shadow: 0 0 0 3px rgba(255, 192, 0, 0.5); } .btn-untappd.disabled, .btn-untappd:disabled { background-color: #ffc000; border-color: #ffc000; } .btn-untappd:active, .btn-untappd.active, .show > .btn-untappd.dropdown-toggle { background-color: #d9a300; background-image: none; border-color: #cc9a00; } .btn-gitlab { color: #fff; background-color: #de7e00; border-color: #de7e00; } .btn-gitlab:hover { color: #fff; background-color: #b86800; border-color: #ab6100; } .btn-gitlab:focus, .btn-gitlab.focus { box-shadow: 0 0 0 3px rgba(222, 126, 0, 0.5); } .btn-gitlab.disabled, .btn-gitlab:disabled { background-color: #de7e00; border-color: #de7e00; } .btn-gitlab:active, .btn-gitlab.active, .show > .btn-gitlab.dropdown-toggle { background-color: #b86800; background-image: none; border-color: #ab6100; } .btn-whatsapp { color: #fff; color: #fff; background-color: #25D366; border-color: #25D366; } .btn-whatsapp:hover { color: #fff; background-color: #1fb256; border-color: #1da851; } .btn-whatsapp:focus, .btn-whatsapp.focus { box-shadow: 0 0 0 3px rgba(37, 211, 102, 0.5); } .btn-whatsapp.disabled, .btn-whatsapp:disabled { background-color: #25D366; border-color: #25D366; } .btn-whatsapp:active, .btn-whatsapp.active, .show > .btn-whatsapp.dropdown-toggle { background-color: #1fb256; background-image: none; border-color: #1da851; } /*# sourceMappingURL=bootstrap-social.css.map */ ================================================ FILE: app/assets/stylesheets/general.css ================================================ body { padding-top: 70px; } ================================================ FILE: app/assets/stylesheets/home.scss.erb ================================================ // Place all the styles related to the Home controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ :root { --input-padding-x: .75rem; --input-padding-y: .75rem; } html, body { height: 100%; } @import url(https://fonts.googleapis.com/css?family=Roboto:300); @import url(https://fonts.googleapis.com/css?family=Lato:300); .home { display: -ms-flexbox; display: -webkit-box; display: flex; -ms-flex-align: center; -ms-flex-pack: center; -webkit-box-align: center; align-items: center; -webkit-box-pack: center; justify-content: center; padding-top: 40px; padding-bottom: 40px; font-family: 'Roboto', sans-serif; font-size: 16px; font-weight: 300; color: white; line-height: 30px; text-align: center; background-image: image-url('bg.jpeg'); background-size: cover; background-repeat: no-repeat; background-color: #444444; background-position: 50%; } .form-signin { width: 100%; max-width: 420px; padding: 15px; margin: 0 auto; } .token-qr table { border-width: 0; border-style: none; border-color: #0000ff; border-collapse: collapse; } .token-qr td { border-left: solid 5px #000; padding: 0; margin: 0; width: 0px; height: 5px; } .token-qr td.black { border-color: #000; } .token-qr td.white { border-color: #fff; } ================================================ FILE: app/assets/stylesheets/profile.css ================================================ /* Everything but the jumbotron gets side spacing for mobile first views */ .container-profile { max-width: 730px; min-width: 768px; } ================================================ FILE: app/assets/stylesheets/scaffolds.scss ================================================ body { background-color: #fff; color: #333; font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; } p, ol, ul, td { font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; } pre { background-color: #eee; padding: 10px; font-size: 11px; } a { color: #000; &:visited { color: #666; } &:hover { color: #fff; background-color: #000; } } div { &.field, &.actions { margin-bottom: 10px; } } #notice { color: green; } .field_with_errors { padding: 2px; background-color: red; display: table; } #error_explanation { width: 450px; border: 2px solid red; padding: 7px; padding-bottom: 0; margin-bottom: 20px; background-color: #f0f0f0; h2 { text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; margin-bottom: 0px; background-color: #c00; color: #fff; } ul li { font-size: 12px; list-style: square; } } ================================================ FILE: app/clients/data_dog_client.rb ================================================ class DataDogClient include HTTParty base_uri 'https://api.datadoghq.com/api/v1' def initialize(app_key, api_key) @base_path = 'https://api.datadoghq.com/api/v1' @app_key = app_key @api_key = api_key @headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json', } end def get_user(email) url = append_auth("/user/#{email}") response = self.class.get(url, headers: @headers) if response.success? JSON.parse(response.body)['user'] else {} end end def new_user(email) url = append_auth('/user') response = self.class.post(url, body: { handle: email }.to_json, headers: @headers) if response.success? JSON.parse(response.body)['user'] else {} end end def activate_user(email) url = append_auth("/user/#{email}") response = self.class.put(url, body: { email: email, disabled: false }.to_json, headers: @headers) if response.success? JSON.parse(response.body)['user'] else {} end end def deactivate_user(email) url = append_auth("/user/#{email}") response = self.class.put(url, body: { email: email, disabled: true }.to_json, headers: @headers) if response.success? JSON.parse(response.body)['user'] else {} end end private def append_auth(str) auth_str = "api_key=#{@api_key}&application_key=#{@app_key}" str += str.include?('?') ? "&#{auth_str}" : "?#{auth_str}" str end end ================================================ FILE: app/controllers/admin_controller.rb ================================================ class AdminController < ApplicationController def index; end end ================================================ FILE: app/controllers/api/v1/base_controller.rb ================================================ class ::Api::V1::BaseController < ActionController::Base protect_from_forgery with: :null_session before_action :authenticate_user_from_token! def authenticate_user_from_token! if get_token.nil? || !AccessToken.valid_token(get_token) raise_unauthorized end end protected def current_user access_token = AccessToken.find_token(get_token) access_token.user end private def get_token if params.key?(:access_token) params[:access_token] elsif params.key?(:token) params[:token] elsif request.headers.key?(:Authorization) request.headers[:Authorization].split(' ').last end end def raise_unauthorized head :unauthorized end end ================================================ FILE: app/controllers/api/v1/endpoints_controller.rb ================================================ class ::Api::V1::EndpointsController < ::Api::V1::BaseController before_action :authorize_user def create endpoint = Endpoint.new(endpoint_param) if endpoint.save render json: { id: endpoint.id, path: endpoint.path, method: endpoint.method, } else render json: { status: endpoint.errors }, status: :unprocessable_entity end end def add_group endpoint = Endpoint.find_by(id: params[:id]) if endpoint.nil? return head :not_found end group = Group.find_by(group_param) group_endpoint = GroupEndpoint.new(group: group, endpoint: endpoint) if group_endpoint.save render json: {} else head :unprocessable_entity end end private def authorize_user unless current_user.admin? head :forbidden end end def group_param params.require(:group).permit(:id) end def endpoint_param params.require(:endpoint).permit(:path, :method) end end ================================================ FILE: app/controllers/api/v1/groups_controller.rb ================================================ class ::Api::V1::GroupsController < ::Api::V1::BaseController def create if current_user.admin? @group = Group.new(group_params) if @group.save render json: { id: @group.id, name: @group.name, }, status: :ok else is_taken = @group.errors.details[:name].select { |x| x[:error] == :taken } if !is_taken.blank? existing_group = Group.find_by(name: @group.name) render json: { status: 'group already exist', id: existing_group.id, name: existing_group.name, }, status: :unprocessable_entity else render json: { status: 'error', }, status: :unprocessable_entity end end end end def add_user @group = Group.find_by(id: params[:id]) return head :not_found unless @group.present? return raise_unauthorized unless current_user.admin? || @group.admin?(current_user) user = User.find_by(id: params[:user_id]) return head :unprocessable_entity unless user.present? expiration_date = params[:expiration_date] @group.add_user_with_expiration(params[:user_id], expiration_date) head :no_content end private def group_params params.require(:group).permit(:name) end end ================================================ FILE: app/controllers/api/v1/users_controller.rb ================================================ class ::Api::V1::UsersController < ::Api::V1::BaseController before_action :set_user, only: %i[show update] def create return head :forbidden unless current_user.admin? user = user_params if User.add_temp_user user[:name], user[:email] render json: { status: 'created' }, status: :ok else render json: { status: 'error' }, status: :unprocessable_entity end end def show if @user.present? user_attrs = %w( email id uid name active admin home_dir shell public_key user_login_id product_name ) data = @user.attributes.select { |k, _v| user_attrs.include?(k) } data['groups'] = @user.groups.map { |g| { 'id' => g.gid, 'name' => g.name } } render json: data else head :not_found end end def deactivate endpoint = Endpoint.find_by(path: api_v1_deactivate_user_path(':id'), method: 'PATCH') if !current_user.admin? && !current_user.permitted_endpoint?(endpoint) return head :forbidden end user = User.find_by(id: params[:id]) return head :not_found if user.nil? user.active = false if user.save head :no_content else head :unprocessable_entity end end def update return raise_unauthorized unless current_user.admin? || current_user == @user render json: { success: @user.update_profile(user_params) } end private def set_user @user = if params.key?('email') User.find_by_email(params['email']) elsif params.key?('uid') User.find_by_uid(params['uid']) elsif params.key?('username') if params.key?('active') is_active = [1, true, '1', 'true'].include?(params['active']) User.where(user_login_id: params['username'], active: is_active).take else User.find_by_user_login_id(params['username']) end end end def user_params if params.key?(:user) params.require(:user).permit(:name, :email, :public_key, :product_name) else params.permit(:name, :email, :public_key, :product_name) end end end ================================================ FILE: app/controllers/api/v1/vpns_controller.rb ================================================ class ::Api::V1::VpnsController < ::Api::V1::BaseController before_action :set_vpn, only: [:assign_group] def create if current_user.admin? @vpn = Vpn.new(vpn_params) @vpn.uuid = SecureRandom.uuid if @vpn.save render json: { id: @vpn.id, name: @vpn.name, host_name: @vpn.host_name, ip_address: @vpn.ip_address, }, status: :ok else render json: { status: 'error' }, status: :unprocessable_entity end end end def assign_group if current_user.admin? @vpn.groups.delete_all @vpn.groups << Group.where(id: params[:group_id]).first render json: { status: 'group assigned' }, status: :ok end end private def set_vpn @vpn = Vpn.find(params[:id]) end def vpn_params params.require(:vpn).permit(:name, :host_name, :ip_address) end end ================================================ FILE: app/controllers/api_resources_controller.rb ================================================ class ApiResourcesController < ApplicationController before_action :set_api_resource, only: %i[show edit update destroy regenerate_access_key] before_action :authenticate_user!, except: [:authenticate] before_action :authorize_user, only: %i[regenerate_access_key destroy update] # GET /api_resources # GET /api_resources.json def index @api_resources = ApiResource.where(user: current_user) if !current_user.admin @api_resources = ApiResource.all if current_user.admin end # GET /api_resources/1 # GET /api_resources/1.json def show; end def authenticate # this authenticates and tells whether users is able to access this api or not if ApiResource.authenticate(params[:access_key], params[:access_token]) render json: { result: 0 }, status: :ok else render json: { result: 1 }, status: 401 end end # GET /api_resources/new def new @api_resource = ApiResource.new end # GET /api_resources/1/edit def edit; end # POST /api_resources # POST /api_resources.json def create @api_resource = ApiResource.new(api_resource_params) @api_resource.access_key = ROTP::Base32.random_base32 @api_resource.user = current_user group = Group.create name: "#{@api_resource.name}_api_group" @api_resource.group = group group.add_admin current_user group.save! respond_to do |format| if @api_resource.save format.html { redirect_to api_resource_path(@api_resource.id), notice: 'Api resource was successfully created.', flash: { access_key: @api_resource.access_key } } format.json { render :show, status: :created, location: @api_resource } else format.html { render :new } format.json { render json: @api_resource.errors, status: :unprocessable_entity } end end end # PATCH/PUT /api_resources/1 # PATCH/PUT /api_resources/1.json def update respond_to do |format| if @api_resource.update(api_resource_params) format.html { redirect_to api_resources_path, notice: 'Api resource was successfully updated.' } format.json { render :show, status: :ok, location: @api_resource } else format.html { render :edit } format.json { render json: @api_resource.errors, status: :unprocessable_entity } end end end # DELETE /api_resources/1 # DELETE /api_resources/1.json def destroy @api_resource.group.destroy if @api_resource.group.present? @api_resource.destroy respond_to do |format| format.html { redirect_to api_resources_url, notice: 'Api resource was successfully destroyed.' } format.json { head :no_content } end end # GET /api_resources/q=.json def search if params[:exact] @api_resources = ApiResource.where('name LIKE ?', params[:q].to_s) else @api_resources = ApiResource.where('name LIKE ?', "%#{params[:q]}%") end @api_resources = @api_resources.order('name ASC').limit(20) data = @api_resources.map { |group| { id: group.id, name: group.name } } render json: data end # GET /api_resources/:id/regenerate_access_key def regenerate_access_key @api_resource.access_key = ROTP::Base32.random_base32 respond_to do |format| if @api_resource.save format.html { redirect_to api_resource_path(@api_resource.id), notice: 'Access key regenerated.', flash: { access_key: @api_resource.access_key } } format.json { render :show, status: :ok, location: @api_resource } else format.html { redirect_to api_resource_path(@api_resource.id), notice: 'Access key failed to regenerate.' } format.json { render json: @api_resource.errors, status: :unprocessable_entity } end end end private # Use callbacks to share common setup or constraints between actions. def set_api_resource @api_resource = ApiResource.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def api_resource_params params.require(:api_resource).permit(:name, :access_key, :description, :user_id, :group_id) end def authorize_user unless current_user.admin? || current_user == @api_resource.user respond_to do |format| format.html { redirect_to api_resources_url, notice: 'Unauthorized access' } format.json { render json: {}, status: :unauthorized } end end end end ================================================ FILE: app/controllers/application_controller.rb ================================================ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception def render_404 respond_to do |format| format.html { render file: "#{Rails.root}/public/404", layout: false, status: :not_found } format.xml { head :not_found } format.any { head :not_found } end end def authenticate_access_token! unless AccessToken.valid_token(params[:token]) render_error(['Unauthorized'], :unauthorized) end end def render_error(errors, status = 400) render 'common/errors', locals: { errors: errors }, status: status end end ================================================ FILE: app/controllers/concerns/.keep ================================================ ================================================ FILE: app/controllers/groups_controller.rb ================================================ class GroupsController < ApplicationController before_action :set_group, only: %i[show edit update destroy add_user add_machine add_vpn add_admin remove_admin delete_user delete_vpn delete_machine] before_action :authenticate_user! def index @groups = [] @group_search = params[:group_search] if current_user.admin && @group_search.present? @groups = Group.where('name LIKE ?', "%#{@group_search}%") elsif current_user.group_admin? && !current_user.admin @groups = GroupAdmin.where(user_id: current_user.id).map(&:group) end end def create if current_user.admin? @group = Group.new(group_params) respond_to do |format| if @group.save format.html { redirect_to group_path(@group), notice: 'Group was successfully created.' } format.json { render status: :created, json: "#{@group.name}host created" } else format.html { redirect_to groups_path, notice: "Can't save '#{group_params[:name]}'" } format.json { render status: :error, json: "#{@group.name} not created" } end end else format.html { redirect_to groups_path, notice: "Can't save '#{group_params[:name]}'" } format.json { render status: :error, json: "#{@group.name} not created" } end end def new @group = Group.new end def show @group_users = User. select(%Q{ users.id AS id, name, email, active, group_associations.created_at AS join_date, group_associations.expiration_date AS group_expiration_date }). joins('LEFT OUTER JOIN group_associations ON users.id = group_associations.user_id'). where('group_associations.group_id = ?', @group.id) end def delete_machine @machine = HostMachine.find(params[:host_machine_id]) if current_user.admin? @machine.groups.delete(@group) end redirect_to group_path @group end def delete_user if current_user.admin? || @group.admin?(current_user) @user = User.find(params[:user_id]) if @user.email.split('@').first != @group.name @group.remove_user @user end end redirect_to group_path(@group, anchor: 'group_members') end def add_user if current_user.admin? || @group.admin?(current_user) user = User.find(params[:user_id]) begin expiration_date = expiration_date_param rescue ArgumentError return respond_to do |format| format.html { redirect_to group_path(@group), notice: 'Expiration date is wrong' } end end @group.add_user_with_expiration(user.id, expiration_date) if user.present? end respond_to do |format| format.html do redirect_to group_path(@group, anchor: 'group_members') end end end def add_machine if current_user.admin? machine = HostMachine.find(params[:machine_id]) machine.groups << @group if machine.present? && machine.groups.find_by_id(@group.id).blank? machine.save! end respond_to do |format| format.html do redirect_to group_path @group end end end def add_admin if current_user.admin? GroupAdmin.find_or_create_by(group_id: @group.id, user_id: params[:user_id]) end respond_to do |format| format.html do redirect_to group_path @group end end end def remove_admin if current_user.admin? GroupAdmin.delete(params[:group_admin_id]) end respond_to do |format| format.html do redirect_to group_path @group end end end def add_vpn if current_user.admin? VpnGroupAssociation.find_or_create_by(group_id: @group.id, vpn_id: params[:vpn_id]) end respond_to do |format| format.html do redirect_to group_path @group end end end def delete_vpn return unless current_user.admin? || @group.group_admin.user == current_user VpnGroupAssociation.where(group_id: @group.id, vpn_id: params[:vpn_id]).destroy_all VpnGroupUserAssociation.where(group_id: @group.id, vpn_id: params[:vpn_id]).destroy_all respond_to do |format| format.html do redirect_to group_path @group end end end def add_group user_id = params[:id] if current_user.admin? begin expiration_date = expiration_date_param rescue ArgumentError response_message = 'Expiration date is wrong' return redirect_to user_path, notice: response_message end group = Group.find(params[:group_id]) group.add_user_with_expiration(user_id, expiration_date) end redirect_to user_path end def delete_group @user = User.find(params[:user_id]) if current_user.admin? group = Group.find(params[:id]) if @user.email.split('@').first != group.name @user.groups.delete(group) end end redirect_to user_path(@user) end def list @groups = [] @group_search = params[:group_search] return unless @group_search.present? if current_user.admin? @groups = Group.where('name LIKE ?', "%#{@group_search}%") elsif current_user.group_admin? @groups = GroupAdmin.where(user_id: current_user.id).map(&:group) end end def search @groups = Group. where('name LIKE ?', "%#{params[:q]}%"). order('name ASC'). limit(20) data = @groups.map { |group| { id: group.id, name: group.name } } render json: data end private # Use callbacks to share common setup or constraints between actions. def set_group @group = Group.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def group_params params.require(:group).permit(:name) end def expiration_date_param expiration_date = params[:expiration_date] return nil if expiration_date.nil? || expiration_date.empty? Date.parse(expiration_date, '%Y-%m-%d') end end ================================================ FILE: app/controllers/home_controller.rb ================================================ class HomeController < ApplicationController before_action :check_signed_in def check_signed_in redirect_to profile_path if signed_in? end def index; end end ================================================ FILE: app/controllers/host_controller.rb ================================================ class HostController < ApplicationController before_action :authenticate_user! def add_host @user = User.find(params[:id]) if current_user.admin? host = Host.new host.user = @user host.host_pattern = params[:host_pattern] host.save! end redirect_to user_path end def delete_host @user = User.find(params[:user_id]) if current_user.admin? @host = Host.find(params[:id]) @host.deleted_by = current_user.id @host.save! @host.destroy end redirect_to user_path(@user) end end ================================================ FILE: app/controllers/host_machine_groups_controller.rb ================================================ class HostMachineGroupsController < ApplicationController def show @host_machines = HostMachine.all end def create @host_machine = HostMachine.new(host_machine_params) respond_to do |format| @host_machine.save format.html { redirect_to :show, notice: 'host_machine was successfully created.' } format.json { render status: :created, json: "#{@host_machine.name}host created" } end end private # Use callbacks to share common setup or constraints between actions. def set_host_machine @host_machine = host_machine.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def host_machine_params params.require(:host_machine).permit(:name) end end ================================================ FILE: app/controllers/host_machines_controller.rb ================================================ class HostMachinesController < ApplicationController before_action :set_host_machine, only: %i[add_group show edit update destroy delete_group] before_action :authenticate_user! before_action :authorize_user, only: %i[delete_group update] def index @title = 'Host' @host_machines = HostMachine.all @host_machines = [] @host_machine_search = params[:host_machine_search] if @host_machine_search.present? if current_user.admin? @host_machines = HostMachine.where('name LIKE ?', "%#{@host_machine_search}%") end end end def create @host_machine = HostMachine.new(host_machine_params) respond_to do |format| if @host_machine.save format.html { redirect_to host_machines_path, notice: 'Host was successfully created.' } format.json { render status: :created, json: "#{@host_machine.name}host created" } else format.html { redirect_to host_machines_path, notice: "Can't save '#{host_machine_params[:name]}'" } format.json { render status: :error, json: "#{@host_machine.name} not created" } end end end def show @machine = @host_machine @groups = Group.all end def update @host_machine.update(default_admins: params[:host_machine][:default_admins]) redirect_to host_machine_path @host_machine end def add_group @machine = @host_machine if current_user.admin? group = Group.find(params[:group_id]) @machine.groups << group if @machine.present? && @machine.groups.find_by_id(group.id).blank? @machine.save! end respond_to do |format| format.html do redirect_to host_machine_path @host_machine end end end def delete_group group = Group.find(params[:group_id]) @host_machine.groups.delete(group) @host_machine.save! redirect_to host_machine_path @host_machine end def search @host_machines = HostMachine. where('name LIKE ?', "%#{params[:q]}%"). order('name ASC'). limit(20) data = @host_machines.map { |host_machine| { id: host_machine.id, name: host_machine.name } } render json: data end private # Use callbacks to share common setup or constraints between actions. def set_host_machine @host_machine = HostMachine.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def host_machine_params params.require(:host_machine).permit(:name) end def authorize_user unless current_user.admin? redirect_to host_machines_path, notice: 'Unauthorized access' end end end ================================================ FILE: app/controllers/nss_controller.rb ================================================ class NssController < ApplicationController skip_before_action :verify_authenticity_token, only: %i[add_host add_user_to_group] before_action :authenticate_access_token!, only: %i[add_host] def host token = AccessToken.valid_token params[:token] @response = nil if token @response = HostMachine.get_group_response(params[:name]) if params[:name].present? render json: @response return end host_machine = HostMachine.find_by(access_key: params[:token]) sysadmins = host_machine.sysadmins if host_machine.present? if sysadmins.present? && sysadmins.count.positive? @response = Group.get_sysadmins_and_groups sysadmins, host_machine.default_admins end render json: @response nil end def add_host if params[:name].present? host = HostMachine.find_or_create_by(name: params[:name]) host.default_admins = params[:default_admins] host.save host.add_host_group(params[:name]) host.add_group(params[:group_name]) render 'add_host', locals: { host: host }, format: :json else errors = ['Name can\'t be blank'] if params.key?(:group_name) && params[:group_name].blank? errors << 'Group Name can\'t be blank' end render_error(errors) end end def group @response = REDIS_CACHE.get("#{GROUP_RESPONSE}:#{params[:token]}") @response = JSON.parse(@response) if @response.present? if @response.blank? host_machine = HostMachine.find_by(access_key: params[:token]) sysadmins = host_machine.sysadmins if host_machine.present? if sysadmins.present? && sysadmins.count.positive? @response = Group.get_sysadmins_and_groups sysadmins, host_machine.default_admins REDIS_CACHE.set("#{GROUP_RESPONSE}:#{params[:token]}", @response.to_json) REDIS_CACHE.expire("#{GROUP_RESPONSE}:#{params[:token]}", REDIS_KEY_EXPIRY) end end render json: @response end def passwd @response = REDIS_CACHE.get("#{PASSWD_RESPONSE}:#{params[:token]}") @response = JSON.parse(@response) if @response.present? if @response.blank? host_machine = HostMachine.find_by(access_key: params[:token]) sysadmins = host_machine.sysadmins if host_machine.present? if sysadmins.present? && sysadmins.count.positive? @response = User.get_sysadmins sysadmins REDIS_CACHE.set("#{PASSWD_RESPONSE}:#{params[:token]}", @response.to_json) REDIS_CACHE.expire("#{PASSWD_RESPONSE}:#{params[:token]}", REDIS_KEY_EXPIRY) end end render json: @response end def shadow token = AccessToken.valid_token params[:token] @response = nil if token name = params[:name] if name.present? @response = REDIS_CACHE.get(SHADOW_NAME_RESPONSE + name) if @response.blank? @response = User.get_shadow_name_response(name).to_json REDIS_CACHE.set(SHADOW_NAME_RESPONSE + name, @response) REDIS_CACHE.expire(SHADOW_NAME_RESPONSE + name, REDIS_KEY_EXPIRY) end else @response = REDIS_CACHE.get(SHADOW_ALL_RESPONSE) if @response.blank? @response = User.get_all_shadow_response.to_json REDIS_CACHE.set(SHADOW_ALL_RESPONSE, @response) REDIS_CACHE.expire(SHADOW_ALL_RESPONSE, REDIS_KEY_EXPIRY) end end end render json: @response end def groups_list token = AccessToken.valid_token params[:token] if token user = User.get_user(params[:email].split('@').first) if user.blank? render json: { success: false } else groups = user.blank? ? [] : user.group_names_list render json: { success: true, groups: groups } end else render json: { success: false } end end end ================================================ FILE: app/controllers/organisations_controller.rb ================================================ class OrganisationsController < ApplicationController before_action :authorize_user, except: [:index] before_action :load_org, only: %i( config_saml_app update show setup_saml save_config_saml_app remove_user_saml_app add_user_saml_app ) before_action :validate_app_name, only: %i( config_saml_app save_config_saml_app remove_user_saml_app add_user_saml_app ) def index render :index, locals: { org_list: Organisation.all } end def new render :new, locals: { org: Organisation.new } end def config_saml_app app_name = params[:app_name] saml_app = app_name.titleize.constantize.new(@org.id) config = saml_app.config users = config.persisted? ? config.group.users : [] render :config_saml_app, locals: { org: @org, saml_config: config, app_name: app_name, users: users, } end def save_config_saml_app app_name = params[:app_name] saml_app_config = params[:saml_app_config] saml_app = app_name.titleize.constantize.new(@org.id) saml_app.save_config(saml_app_config[:sso_url], params[:config]) flash[:success] = 'Configuration saved successfully' redirect_to organisation_config_saml_app_path( app_name: app_name, organisation_id: @org.id ) end def remove_user_saml_app app_name = params[:app_name] saml_app = app_name.titleize.constantize.new(@org.id) if saml_app.remove_user(params[:email]) flash[:success] = 'User removed successfullly' else flash[:error] = 'Issue removing the user' end redirect_to organisation_config_saml_app_path end def add_user_saml_app app_name = params[:app_name] saml_app = app_name.titleize.constantize.new(@org.id) if saml_app.add_user(params[:email]) flash[:success] = 'User added successfullly' else flash[:error] = 'Issue adding the user' end redirect_to organisation_config_saml_app_path end def create org = Organisation.setup(organisation_params.to_h || {}) if org.errors.blank? flash[:success] = 'Successfully created organisation' redirect_to organisations_path else flash[:errors] = org.errors.full_messages render :new, locals: { org: org } end end def update @org.update_profile(organisation_params.to_h || {}) if @org.errors.blank? flash[:success] = 'Successfully updated organisation' redirect_to organisations_path else flash[:errors] = @org.errors.full_messages render :show, locals: { org: @org } end end def show render :show, locals: { org: @org } end def setup_saml if @org.saml_setup? flash[:errors] = 'SAML Certificates Already Setup' else @org.setup_saml_certs flash[:success] = 'Successfully setup SAML Certificates' end redirect_to organisations_path end private def authorize_user unless current_user.admin? respond_to do |format| format.html { redirect_to organisations_path, notice: 'Unauthorized access' } format.json { render json: {}, status: :unauthorized } end end end def load_org id = params[:id] || params[:organisation_id] @org = Organisation.where(id: id).first if @org.blank? redirect_to organisations_path end end def validate_app_name saml_apps = ENV['SAML_APPS'].split(',').map(&:downcase) unless saml_apps.include?(params[:app_name].downcase) redirect_to organisation_path(id: params[:organisation_id]) end end def organisation_params params.require(:organisation).permit( :name, :website, :domain, :country, :state, :address, :admin_email_address, :slug, :unit_name ) end end ================================================ FILE: app/controllers/pings_controller.rb ================================================ class PingsController < ApplicationController def show render plain: 'pong' end end ================================================ FILE: app/controllers/profile_controller.rb ================================================ class ProfileController < ApplicationController require 'vpn/mobileconfig' skip_before_action :verify_authenticity_token, if: Proc.new { |c| c.request.format == 'application/json' } before_action :authenticate_user!, except: %i[user_id verify authenticate authenticate_cas authenticate_ms_chap authenticate_pam public_key] def regen_auth current_user.generate_two_factor_auth(true) redirect_to profile_path end def show @token_qr = nil unless current_user.provisioning_uri.blank? @token_qr = RQRCode::QRCode.new(current_user.provisioning_uri, size: 10, level: :h) end end def user_admin @users = [] @groups = [] if !current_user.admin? redirect_to profile_path end @user_search = params[:user_search] if @user_search.present? @users = User.where('name LIKE ? OR email LIKE ?', "%#{@user_search}%", "%#{@user_search}%"). take(5) redirect_to profile_list_path(user_search: params[:user_search]) if @users.count.positive? end @group_search = params[:group_search] if @group_search.present? @groups = Group.where('name LIKE ?', "%#{@group_search}%").take(5) redirect_to group_list_path(group_search: params[:group_search]) if @groups.count.positive? end end def group_admin @users = [] @groups = [] if !current_user.admin? && !current_user.group_admin? redirect_to profile_path end @user_search = params[:user_search] if @user_search.present? @users = User.where('name LIKE ? OR email LIKE ?', "%#{@user_search}%", "%#{@user_search}%"). take(5) redirect_to profile_list_path(user_search: params[:user_search]) if @users.count.positive? end @group_search = params[:group_search] if @group_search.present? @groups = Group.where('name LIKE ?', "%#{@group_search}%").take(5) redirect_to group_list_path(group_search: params[:group_search]) if @groups.count >= 0 end end def user_id token = AccessToken.valid_token params[:token] response = 0 if token user = User.get_user(params[:name]) response = user.uid if user.present? end render plain: response end def download_vpn if !Pathname.new("/opt/vpnkeys/#{current_user.email}.tar.gz").exist? `cd /etc/openvpn/easy-rsa/ && bash /etc/openvpn/easy-rsa/gen-client-keys #{current_user.email}` else `cd /etc/openvpn/easy-rsa/ && bash /etc/openvpn/easy-rsa/gen-client-conf #{current_user.email}` end send_file( "/opt/vpnkeys/#{current_user.email}.tar.gz", type: 'application/zip', disposition: "attachment; filename=#{current_user.email}.tar.gz" ) end def download_vpn_for_ios_and_mac mobileconfig = Mobileconfig.new vpns = Vpn.user_vpns current_user return render plain: "you don't have access to any vpns" unless vpns.present? mobileconfig_data = mobileconfig.generate(vpns, current_user) send_data( mobileconfig_data, filename: "#{current_user.email}.mobileconfig", type: 'application/x-apple-aspen-config' ) end def download_vpn_for_user render plain: 'Please download vpn config from your homepage' end def authenticate response = User.authenticate params if response render plain: 0 else render plain: 1 end end def authenticate_ms_chap response = User.ms_chap_auth params render plain: response end def authenticate_cas username = User.authenticate_cas request.env['HTTP_AUTHORIZATION'] ## cas-5.2.x expects {"@c":".SimplePrincipal","id":"casuser","attributes":{}} response_map = { '@class': 'org.apereo.cas.authentication.principal.SimplePrincipal', 'id': username, 'attributes': { 'backend': 'gate-sso' }, } if username.present? render json: response_map, status: :ok else response_map['attributes'] = nil render json: response_map, status: 401 end end def authenticate_pam response = User.authenticate_pam params if response render plain: 0 else render plain: 1 end end def verify token = AccessToken.valid_token params[:token] if token response = User.verify params if response render plain: 0 else render plain: 1 end else render plain: 1 end end def list @users = [] @user_search = params[:user_search] if @user_search.present? @users = User.where('name LIKE ? OR email LIKE ?', "%#{@user_search}%", "%#{@user_search}%"). take(5) end end def admin @users = [] @groups = [] if !current_user.admin? redirect_to profile_path end @user_search = params[:user_search] if @user_search.present? @users = User.where('name LIKE ? OR email LIKE ?', "%#{@user_search}%", "%#{@user_search}%"). take(5) redirect_to profile_list_path(user_search: params[:user_search]) if @users.count.positive? end @group_search = params[:group_search] if @group_search.present? @groups = Group.where('name LIKE ?', "%#{@group_search}%").take(5) redirect_to group_list_path(group_search: params[:group_search]) if @groups.count.positive? end end def update if current_user.admin? @user = User.find(params[:id]) @user.update(admin_active) end redirect_to user_path end def user_edit; end def public_key_update @user = User.where(id: params[:id]).first if current_user.admin? || current_user.id == @user.id @user.public_key = params[:public_key] @user.save! end redirect_to user_path end def public_key public_key = '' @user = User.get_user(params[:name]) public_key = @user.public_key if @user.present? render plain: public_key end def user @group = Group.all @user = User.where(id: params[:id]).first if !current_user.admin? && current_user.id != @user.id redirect_to profile_path end render_404 if @user.blank? if @user.present? # hack add blank text to public_key @user.public_key = 'Add public key' if @user.public_key.blank? respond_to do |format| format.html end end end protected def admin_active params.require(:user).permit(:active, :admin) end end ================================================ FILE: app/controllers/saml_idp_controller.rb ================================================ #TODO rename back to SAMLController #class SamlIdpController < SamlIdp::IdpController class SamlIdpController < ApplicationController layout false before_action :setup_saml_configuration def show xml_content = SamlIdp.metadata.signed if params.key?(:download) send_data xml_content, type: 'text/xml', filename: 'metadata.xml' else render xml: xml_content end end private def idp_authenticate(email, password) user = User.find_and_validate_saml_user(email, password, params[:app]) user.present? ? user : nil end def idp_make_saml_response(found_user) encode_response found_user end def idp_logout # user = User.by_email(saml_request.name_id) # user.logout end def setup_saml_configuration slug = params[:slug] app = params[:app] org = Organisation.find_by_slug(slug) saml_url = "#{ENV['GATE_SERVER_URL']}/#{slug}/#{app}/saml" SamlIdp.configure do |config| config.x509_certificate = org.cert_key config.secret_key = org.cert_private_key config.organization_name = org.name config.organization_url = org.website config.base_saml_location = saml_url config.session_expiry = 86400 config.name_id.formats = { email_address: ->(principal) { principal.email }, transient: ->(principal) { principal.user_login_id }, persistent: ->(principal) { principal.user_login_id }, name: ->(principal) { principal.name }, } config.attributes = { 'eduPersonPrincipalName' => { 'name' => 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6', 'name_format' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'getter' => ->(principal) { principal.email }, }, } config.attribute_service_location = "#{saml_url}/attributes" config.single_service_post_location = "#{saml_url}/auth" config.single_logout_service_post_location = "#{saml_url}/logout" config.single_logout_service_redirect_location = "#{saml_url}/logout" end end end ================================================ FILE: app/controllers/users/auth_controller.rb ================================================ class Users::AuthController < ApplicationController def log_in unless ENV['SIGN_IN_TYPE'] == 'form' return redirect_to root_path end email = params.require(:email) name = params.require(:name) unless User.valid_domain? email.split('@').last return render plain: 'Your domain is unauthorized', status: :unauthorized end user = User.create_user(name, email) user.generate_two_factor_auth sign_in_and_redirect user, event: :authentication end end ================================================ FILE: app/controllers/users/omniauth_callbacks_controller.rb ================================================ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def google_oauth2 # You need to implement the method below in your model (e.g. app/models/user.rb) # data = request.env['omniauth.auth'] domain = data['info']['email'].split('@').last unless User.valid_domain? domain return render plain: 'Your domain is unauthorized', status: :unauthorized end @user = User.create_user(data.info['name'], data.info['email']) if @user.persisted? @user.generate_two_factor_auth sign_in_and_redirect @user, event: :authentication else session['devise.google_data'] = request.env['omniauth.auth'] redirect_to new_user_registration_url end end end ================================================ FILE: app/controllers/users_controller.rb ================================================ class UsersController < ApplicationController before_action :authenticate_user!, except: %i[user_id verify authenticate authenticate_cas authenticate_ms_chap authenticate_pam public_key] before_action :authorize_user, only: %i[create update] def index @user_search = params[:user_search] @users = [] @users = User.where('name like ?', "%#{@user_search}%").take(20) if @user_search.present? end def show @user = User.where(id: params[:id]).first return render_404 if @user.blank? @user_groups = Group. select(%{ groups.id AS id, gid, name, deleted_at, group_associations.expiration_date AS group_expiration_date }). joins('INNER JOIN group_associations ON groups.id = group_associations.group_id'). where('group_associations.user_id = ?', @user.id) if @user.access_token.blank? access_token = AccessToken.new access_token.token = ROTP::Base32.random_base32 access_token.user = @user access_token.save! end @vpns = Vpn.user_vpns @user return unless current_user.admin? || current_user == @user return unless @user.present? && (current_user.admin? || current_user.id == @user.id) respond_to do |format| format.html { render :show, flash: { token: access_token.try(:token) } } end end def new return redirect_to profile_path unless current_user.admin? render :new, locals: { roles: ENV['USER_ROLES'].split(','), domains: ENV['GATE_HOSTED_DOMAINS'].split(','), } end def create user = User.add_user( user_params[:first_name], user_params[:last_name], user_params[:user_role], params[:user_domain] ) if user.errors.present? flash[:errors] = user.errors.full_messages redirect_to(new_user_path) else flash[:success] = 'Successfully Created User' redirect_to user_path(id: user.id) end end def update @user = User.find(params[:id]) begin @user.update(product_name: product_name) response_message = 'product name updated successfully!!' rescue ActionController::ParameterMissing response_message = 'Params are missing' end form_response(response_message) end def search @users = User. where('name LIKE :q OR email LIKE :q', q: "%#{params[:q]}%"). order('name ASC'). limit(20) unless params[:include_inactive] == 'true' @users = @users.where(active: true) end data = @users.map do |user| { id: user.id, name: user.name, email: user.email, name_email: "#{user.name} - #{user.email}", } end render json: data end # GET /users/:id/regenerate_token def regenerate_token @user = User.find(params[:id]) unless current_user.admin? || (current_user.id == @user.id) return respond_to do |format| format.html { redirect_to user_path(@user.id), notice: 'You cannot regenerate this token.' } format.json { render json: @user.errors, status: :unauthorized } end end @access_token = @user.access_token @access_token.token = ROTP::Base32.random_base32 respond_to do |format| if @access_token.save format.html { redirect_to user_path(@user.id), notice: 'Token regenerated.', flash: { token: @access_token.token } } format.json { render :show, status: :ok, location: @user } else format.html { redirect_to user_path(@user.id), notice: 'Token failed to regenerate.' } format.json { render json: @user.errors, status: :unprocessable_entity } end end end private def user_params params.require(:user).permit( :first_name, :last_name, :user_role ) end def form_response(message) respond_to do |format| format.html { redirect_to user_path, notice: message } end end def product_name params.require(:product_name) end def authorize_user unless current_user.admin? flash[:errors] = 'unauthorized access' redirect_to profile_path end end end ================================================ FILE: app/controllers/vpn_domain_name_servers_controller.rb ================================================ class VpnsController < ApplicationController before_action :authorize_user before_action :set_vpn, only: %i[show edit update destroy user_associated_groups] def destroy; end end #TODO fix this in future. This is something not great. class VpnDomainNameServersController < ApplicationController end ================================================ FILE: app/controllers/vpns_controller.rb ================================================ class VpnsController < ApplicationController before_action :authorize_user, except: %i[create_group_associated_users show index user_associated_groups group_associated_users search] before_action :set_vpn, only: %i[show edit update destroy user_associated_groups add_dns_server remove_dns_server add_search_domain remove_search_domain add_supplemental_match_domain remove_supplemental_match_domain migrate_to_new_group assign_group] before_action :authenticate_user! require 'securerandom' def index @vpns = Vpn.order(:name) end def update if current_user.admin? @vpn = Vpn.find(params[:id]) if @vpn.update(vpn_params) redirect_to vpn_path(@vpn), notice: 'Vpn was successfully updated.' end else redirect_to vpn_path(@vpn), notice: 'You can not update, not sufficient privileges.' end end def create @vpn = Vpn.new(vpn_params) @vpn.uuid = SecureRandom.uuid respond_to do |format| if @vpn.save format.html { redirect_to vpns_path, notice: 'Vpn was successfully added.' } format.json { render status: :created, json: "#{@vpn.name}host created" } else format.html { redirect_to vpns_path, notice: "Can't save '#{vpn_params[:name]}'" } format.json { render status: :error, json: "#{@vpn.name} not created" } end end end def new @vpn = Vpn.new end def add_dns_server if current_user.admin? && params[:server_address].present? VpnDomainNameServer.find_or_create_by(server_address: params[:server_address], vpn: @vpn) end redirect_to vpn_path(@vpn, anchor: 'dns_hosts') end def add_search_domain if current_user.admin? && params[:search_domain].present? VpnSearchDomain.find_or_create_by(search_domain: params[:search_domain], vpn: @vpn) end redirect_to vpn_path(@vpn, anchor: 'search_domains') end def add_supplemental_match_domain if current_user.admin? && params[:supplemental_match_domain].present? VpnSupplementalMatchDomain.find_or_create_by( supplemental_match_domain: params[:supplemental_match_domain], vpn: @vpn ) end redirect_to vpn_path(@vpn, anchor: 'match_domains') end def remove_dns_server if current_user.admin? VpnDomainNameServer.delete(params[:vpn_domain_name_server_id]) end redirect_to vpn_path(@vpn, anchor: 'dns_hosts') end def remove_search_domain if current_user.admin? VpnSearchDomain.delete(params[:vpn_search_domain_id]) end redirect_to vpn_path(@vpn, anchor: 'search_domains') end def remove_supplemental_match_domain if current_user.admin? VpnSupplementalMatchDomain.delete(params[:vpn_supplemental_match_domain_id]) end redirect_to vpn_path(@vpn, anchor: 'match_domains') end def assign_group if current_user.admin? @vpn.groups.delete_all @vpn.groups << Group.where(id: params[:group_id]).first end redirect_to vpn_path(@vpn, anchor: 'match_domains') end def show @vpn = Vpn.find(params[:id]) @groups = Group.order(:name) end def user_associated_groups @groups_under_current_user = [] @group_id = params[:group_id] @vpn_id = params[:id] if current_user.admin? @groups_under_current_user = Group.all else @vpn.groups.each do |vpn_group| if vpn_group.group_admin.try(:user) == current_user @groups_under_current_user << vpn_group end end end render 'show' end def group_associated_users @group = Group.find(params[:group_id]) if current_user.admin? || @group.group_admin.try(:user) == current_user @users = @group.users @vpn_group_user_associations = VpnGroupUserAssociation.where( vpn_id: params[:vpn_id], group_id: params[:group_id] ) @vpn_enabled_users = @vpn_group_user_associations.map(&:user) @vpn_disabled_users = @users - @vpn_enabled_users @vpn_enabled_users = @vpn_enabled_users.sort_by(&:email) @vpn_disabled_users = @vpn_disabled_users.sort_by(&:email) end respond_to do |format| format.json { render status: :ok, json: { enabled: @vpn_enabled_users, disabled: @vpn_disabled_users } } end end def create_group_associated_users if current_user.admin? || VpnGroupAssociation.find_by_vpn_id_and_group_id( params[:vpn_id].to_i, params[:group_id] ).group.group_admin.user == current_user @users_selected = params[:users] || [] @associations_made = [] VpnGroupUserAssociation.where( vpn_id: params[:vpn_id].to_i, group_id: params[:group_id].to_i ).each do |vpn_association| unless @users_selected.include? vpn_association.user.id vpn_association.destroy end end @users_selected.each do |user| @associations_made << VpnGroupUserAssociation.find_or_create_by( vpn_id: params[:vpn_id], group_id: params[:group_id], user_id: user.to_i ) end respond_to do |format| format.json { render status: :ok, json: @associations_made } end else respond_to do |format| format.json { render status: :unauthorized, json: 'not gonna happen' } end end end def destroy VpnGroupUserAssociation.where(vpn_id: params[:id]).destroy_all VpnGroupAssociation.where(vpn_id: params[:id]).destroy_all Vpn.destroy(params[:id]) respond_to do |format| format.html { redirect_to vpns_path, notice: 'Vpn was successfully destroyed.' } format.json { render status: :ok, json: 'vpn destroyed' } end end def search @vpns = Vpn. where('name LIKE ?', "%#{params[:q]}%"). order('name ASC'). limit(20) data = @vpns.map { |vpn| { id: vpn.id, name: vpn.name } } render json: data end private def set_vpn @vpn = Vpn.find(params[:id]) end def vpn_params params.require(:vpn).permit(:name, :host_name, :ip_address) end def authorize_user unless current_user.admin? redirect_to profile_path end end end ================================================ FILE: app/helpers/admin_helper.rb ================================================ module AdminHelper end ================================================ FILE: app/helpers/api_resources_helper.rb ================================================ module ApiResourcesHelper end ================================================ FILE: app/helpers/application_helper.rb ================================================ module ApplicationHelper def add_placeholder_to_list(list, placeholder, string_convert: 'titleize') (list.map do |row| name = string_convert.present? ? row.send(string_convert.to_sym) : row [name, row] end).insert(0, [placeholder, '']) end end ================================================ FILE: app/helpers/group_helper.rb ================================================ module GroupHelper end ================================================ FILE: app/helpers/groups_helper.rb ================================================ module GroupsHelper end ================================================ FILE: app/helpers/home_helper.rb ================================================ module HomeHelper end ================================================ FILE: app/helpers/host_access_groups_helper.rb ================================================ module HostAccessGroupsHelper end ================================================ FILE: app/helpers/host_machine_groups_helper.rb ================================================ module HostMachineGroupsHelper end ================================================ FILE: app/helpers/host_machines_helper.rb ================================================ module HostMachinesHelper end ================================================ FILE: app/helpers/nss_helper.rb ================================================ module NssHelper end ================================================ FILE: app/helpers/omniauth_callbacks_helper.rb ================================================ module OmniauthCallbacksHelper end ================================================ FILE: app/helpers/profile_helper.rb ================================================ module ProfileHelper end ================================================ FILE: app/helpers/users_helper.rb ================================================ module UsersHelper end ================================================ FILE: app/lib/datadog.rb ================================================ class Datadog < SamlApp def initialize(org_id) @app_name = 'datadog' super(org_id) if @config.persisted? @client = DataDogClient.new( @config.config['app_key'], @config.config['api_key'] ) else @config.config = { app_key: '', api_key: '' } end end def save_config(sso_url, config = {}) @config.config = @config.config.merge(config) super(sso_url, config) end def add_user(email) user_detail_response = @client.get_user(email) response = if user_detail_response.eql?({}) @client.new_user(email) else @client.activate_user(email) end super(email) unless response.eql?({}) end def remove_user(email) response = @client.deactivate_user(email) super(email) unless response.eql?({}) end end ================================================ FILE: app/lib/saml_app.rb ================================================ class SamlApp attr_accessor :config, :app_name def initialize(org_id) @config = SamlAppConfig.find_or_initialize_by( app_name: @app_name, organisation_id: org_id ) end def save_config(sso_url, config = {}) unless @config.persisted? group_name = "#{@config.organisation.slug}_saml_#{app_name}_users" @config.group = Group.find_or_create_by(name: group_name) end @config.sso_url = sso_url @config.save end def add_user(email) user = User.where(email: email).first unless user.blank? @config.group.add_user(user.id) return true end false end def remove_user(email) user = User.where(email: email).first unless user.blank? @config.group.remove_user(user.id) return true end false end end ================================================ FILE: app/mailers/.keep ================================================ ================================================ FILE: app/models/.keep ================================================ ================================================ FILE: app/models/access_token.rb ================================================ class AccessToken < ApplicationRecord attr_accessor :token belongs_to :user before_save :hash_token! def self.find_token challenge_token AccessToken.where(hashed_token: Digest::SHA512.hexdigest(challenge_token)).first end def self.valid_token challenge_token find_token(challenge_token).present? end private def hash_token! self.hashed_token = Digest::SHA512.hexdigest self.token unless self.token.blank? end end ================================================ FILE: app/models/api_resource.rb ================================================ class ApiResource < ApplicationRecord attr_accessor :access_key validates :name, format: { with: /\A[a-zA-Z0-9_-]+\Z/ }, uniqueness: true, presence: true validates :access_key, presence: true, on: :create belongs_to :user belongs_to :group before_save :hash_access_key! def self.authenticate access_key, access_token api_resource = ApiResource.find_by(hashed_access_key: Digest::SHA512.hexdigest(access_key)) user = AccessToken.find_by(hashed_token: Digest::SHA512.hexdigest(access_token)).user api_resource.group.member? user end private def hash_access_key! self.hashed_access_key = Digest::SHA512.hexdigest self.access_key unless self.access_key.blank? end end ================================================ FILE: app/models/application_record.rb ================================================ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end ================================================ FILE: app/models/concerns/.keep ================================================ ================================================ FILE: app/models/concerns/ms_chap_auth.rb ================================================ module MsChapAuth def hexlify(msg) msg.unpack('H*').first end def unhexlify(msg) msg.scan(/../).collect { |c| c.to_i(16).chr }.join end def test_key key, challenge des = OpenSSL::Cipher::DES.new('ECB') des.encrypt des.key = key des.update challenge #need to do it twice, don't know why!!! :-( des.update challenge end def ntlm_challenge_response word, challenge uword = word.encode('iso-8859-1').encode('utf-16le') ntlmhash = md4_hash uword response = [] response.push(test_key(key56_to_key64(ntlmhash[0...14]), challenge)) response.push(test_key(key56_to_key64(ntlmhash[14...28]), challenge)) response.push(test_key(key56_to_key64(ntlmhash[28...ntlmhash.length] + '0000000000'), challenge)) hexlify(response[0]) + hexlify(response[1]) + hexlify(response[2]) end def md4_hash word md4 = OpenSSL::Digest::MD4.new return md4.hexdigest(word) end def set_key_odd_parity key for pos in 0..key.length - 1 for k in 0..6 bit = 0 t = key[pos] >> k bit = (t ^ bit) & 0x1 end key[pos] = (key[pos] & 0xFE) | bit end return key end def key56_to_key64 key_raw key_raw = unhexlify(key_raw) key_56 = [] key_raw.split("").each {|c| key_56.push(c.ord)} key = [] (0..7).to_a.each {|i| key.push(0)} key[0] = key_56[0] key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); key[7] = (key_56[6] << 1) & 0xFF; key = set_key_odd_parity(key) keyout = '' key.each {|k| keyout += k.chr} return keyout end def nt_password_hash password md4 = OpenSSL::Digest::MD4.new md4.digest(password) end def get_nt_key password unicode_pwd = password.encode('iso-8859-1').encode('utf-16le') pwd_hash = nt_password_hash(unicode_pwd) nt_key = nt_password_hash(pwd_hash) return hexlify(nt_key) end def authenticate_ms_chap password, challenge, response if ntlm_challenge_response(password, unhexlify(challenge)) == response return "NT_KEY: " + get_nt_key(password).upcase end return ("NT_STATUS_UNSUCCESSFUL: Failure (0xC0000001)") end def authenticate_ms_chap_with_drift passwords, challenge, response passwords.each do |password| if ntlm_challenge_response(password, unhexlify(challenge)) == response return "NT_KEY: " + get_nt_key(password).upcase end end return ("NT_STATUS_UNSUCCESSFUL: Failure (0xC0000001)") end end ================================================ FILE: app/models/endpoint.rb ================================================ class Endpoint < ApplicationRecord has_many :group_endpoints has_many :groups, through: :group_endpoints validates_presence_of :path validates_presence_of :method validates :method, inclusion: { in: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] } validates_format_of :path, with: /\A((\/(([0-9, a-z,\-,_]+)|(:[a-z]+))+)+|\/)\Z/i end ================================================ FILE: app/models/group.rb ================================================ class Group < ApplicationRecord has_many :group_admins, dependent: :destroy has_many :group_associations has_many :users, through: :group_associations has_many :vpn_group_associations has_many :vpns, through: :vpn_group_associations has_many :host_access_groups has_many :host_machines, through: :host_access_groups belongs_to :vpn has_many :group_endpoints has_many :endpoints, through: :group_endpoints validates_uniqueness_of :name, case_sensitive: false validates :name, presence: true before_create :set_lower_case_name acts_as_paranoid after_create :add_gid GID_CONSTANT = 9000 def burst_host_cache if host_machines.count.positive? host_machines.each do |host| if host.access_key.present? REDIS_CACHE.del "#{GROUP_RESPONSE}:#{host.access_key}" REDIS_CACHE.del "#{PASSWD_RESPONSE}:#{host.access_key}" Rails.logger.info "hello #{host.name} #{host.access_key}" end end end end def add_admin(user) GroupAdmin.find_or_create_by(group_id: id, user: user) end def set_lower_case_name self.name = name.downcase end def add_gid self.gid = id + GID_CONSTANT save! end def self.get_name_response(name) response = REDIS_CACHE.get(GROUP_NSS_RESPONSE + name) if response.blank? group = Group.where(name: name).first group = [] if group.blank? response = group.group_response.to_json REDIS_CACHE.set(GROUP_NSS_RESPONSE + name, response) REDIS_CACHE.expire(GROUP_NSS_RESPONSE + name, REDIS_KEY_EXPIRY) end JSON.parse(response, symbolize_names: true) end def self.get_all_response response = REDIS_CACHE.get(GROUP_ALL_RESPONSE) if response.blank? response_array = [] Group.all.includes(:users).each do |group| response_array << group.group_response end response = response_array.to_json REDIS_CACHE.set(GROUP_ALL_RESPONSE, response) REDIS_CACHE.expire(GROUP_ALL_RESPONSE, REDIS_KEY_EXPIRY) end response end def self.get_gid_response(gid) group = Group.where(gid: gid).first return [] if group.blank? group.group_response end def admin?(user) GroupAdmin.where(group_id: self, user_id: user).first.present? end def member?(user) users.exists? user.id end def self.generate_group_response(name, gid, members) { gr_name: name, gr_passwd: 'x', gr_gid: gid, gr_mem: members, } end def group_response Group.group_nss_response name end def self.group_nss_response(name) group_response = REDIS_CACHE.get("#{GROUP_NSS_RESPONSE}:#{name}") group_response = JSON.parse(group_response) if group_response.present? if group_response.blank? group = Group.find_by(name: name) if group.present? members = group.users.map(&:user_login_id) response_hash = Group.generate_group_response(group.name, group.gid, members) REDIS_CACHE.set("#{GROUP_NSS_RESPONSE}:#{group.name}", response_hash.to_json) REDIS_CACHE.expire("#{GROUP_NSS_RESPONSE}:#{group.name}", REDIS_KEY_EXPIRY) group_response = response_hash end end group_response end def self.get_sysadmins_and_groups(sysadmins, default_admins = true) sysadmins_login_ids = User. select(:user_login_id). where('id IN (?)', sysadmins). map(&:user_login_id) # TODO: extract to query object groups = Group. select(%( id, name, gid, ( SELECT GROUP_CONCAT(user_login_id) FROM users INNER JOIN group_associations ON users.id = group_associations.user_id WHERE group_associations.group_id = groups.id ) AS members )). where('name IN (?)', sysadmins_login_ids). map do |group| members = (group.members || '').split(',') Group.generate_group_response(group.name, group.gid, members) end groups << Group.get_default_sysadmin_group_for_host(sysadmins_login_ids, default_admins) groups.to_json end def get_user_ids user_ids = REDIS_CACHE.get("#{GROUP_UID_RESPONSE}:#{name}") user_ids = JSON.parse(user_ids) if user_ids.present? if user_ids.blank? user_ids = users.map(&:user_login_id) REDIS_CACHE.set("#{GROUP_UID_RESPONSE}:#{name}", user_ids.to_json) REDIS_CACHE.expire("#{GROUP_UID_RESPONSE}:#{name}", REDIS_KEY_EXPIRY) end user_ids end def self.get_default_sysadmin_group_for_host(sysadmins_login_ids, default_admins = true) sysadmins = sysadmins_login_ids if default_admins group = Group.find_by(name: 'sysadmins') if group.present? sysadmins = sysadmins + group.get_user_ids end end group_id = group.blank? ? 8999 : group.id sysadmin_group = Group.generate_group_response('sysadmins', group_id, sysadmins.uniq) sysadmin_group end def add_user(user_id) add_user_with_expiration(user_id, nil) end def add_user_with_expiration(user_id, expiration_date) remove_user user_id group_associations.create(user_id: user_id, expiration_date: expiration_date) end def remove_user(user_id) group_associations.where(user_id: user_id).destroy_all burst_host_cache end end ================================================ FILE: app/models/group_admin.rb ================================================ class GroupAdmin < ApplicationRecord belongs_to :user belongs_to :group end ================================================ FILE: app/models/group_association.rb ================================================ class GroupAssociation < ApplicationRecord belongs_to :user belongs_to :group def self.revoke_expired(date = Date.today) where('expiration_date < ?', date).destroy_all end end ================================================ FILE: app/models/group_endpoint.rb ================================================ class GroupEndpoint < ApplicationRecord belongs_to :group belongs_to :endpoint validates :group, uniqueness: { scope: :endpoint } validates_presence_of :group validates_presence_of :endpoint end ================================================ FILE: app/models/host.rb ================================================ class Host < ApplicationRecord belongs_to :user acts_as_paranoid end ================================================ FILE: app/models/host_access_group.rb ================================================ class HostAccessGroup < ApplicationRecord belongs_to :host_machine belongs_to :group end ================================================ FILE: app/models/host_machine.rb ================================================ class HostMachine < ApplicationRecord has_many :host_access_groups has_many :groups, through: :host_access_groups validates_uniqueness_of :name, case_sensitive: false validates :name, presence: true before_create :set_lower_case_name before_create :set_host_access_key def set_host_access_key self.access_key = ROTP::Base32.random_base32 end def set_lower_case_name self.name = self.name.downcase end def self.get_group_response name host_machine = HostMachine.find_by_name(name) response = {} return response if host_machine.blank? response[:host_name] = name response[:groups] = host_machine.groups.collect { |g| g.name } response end def sysadmins users = GroupAssociation. select(:user_id). distinct. joins(:user). where("group_id IN (?)", groups.collect(&:id)). collect(&:user_id) end def add_host_group(name) name = name.squish if name.present? name = "#{name}_host_group" self.add_group(name.downcase) end end def add_group(name) name = name.squish if name.present? group = Group.find_or_initialize_by(name: name.downcase) self.groups << group unless self.groups.include? group self.save end end end ================================================ FILE: app/models/ip_address.rb ================================================ class IpAddress < ApplicationRecord end ================================================ FILE: app/models/organisation.rb ================================================ class Organisation < ApplicationRecord validates :name, :website, :domain, :country, :state, :address, :admin_email_address, :slug, presence: true validates :address, format: { with: /\A[a-zA-Z0-9\s]+\z/, message: 'Invalid - Only Alphabets, Space and Numbers Allowed', } validates :admin_email_address, email: true validates :slug, uniqueness: true attr_accessor :cert, :rsa_key UPDATE_KEYS = %w( name website domain country state address admin_email_address slug unit_name ).freeze def self.find_by_slug(slug) Organisation.where(slug: slug).first end def self.setup(attrs = {}) attrs = attrs.stringify_keys attrs = attrs.select { |k, _v| UPDATE_KEYS.include?(k) } org = Organisation.new(attrs) org.save if org.valid? org end def update_profile(attrs = {}) attrs = attrs.stringify_keys attrs = attrs.select { |k, _v| UPDATE_KEYS.include?(k) } assign_attributes(attrs) save if valid? end def saml_setup? cert_fingerprint.present? && cert_key.present? && cert_private_key.present? end def setup_saml_certs return false unless persisted? require 'openssl' self.rsa_key = OpenSSL::PKey::RSA.new(2048) private_key = rsa_key.to_pem public_key = rsa_key.public_key subject = "/C=#{country}/ST=#{state}/L=#{address}/O=#{name}/OU=#{unit_name}/CN=#{domain}" self.cert = OpenSSL::X509::Certificate.new cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject) cert.not_before = Time.now cert.not_after = Time.now + 365 * 24 * 60 * 60 cert.public_key = public_key cert.serial = SecureRandom.random_number(10) cert.version = 2 ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = cert cert.extensions = [ ef.create_extension('basicConstraints', 'CA:TRUE', true), ef.create_extension('subjectKeyIdentifier', 'hash'), ] cert.add_extension ef.create_extension( 'authorityKeyIdentifier', 'keyid:always,issuer:always' ) cert.sign rsa_key, OpenSSL::Digest::SHA1.new update_attributes( cert_fingerprint: OpenSSL::Digest::SHA256.hexdigest(cert.to_der).scan(/../).join(':'), cert_key: cert.to_pem, cert_private_key: private_key ) end end ================================================ FILE: app/models/saml_app_config.rb ================================================ class SamlAppConfig < ApplicationRecord belongs_to :group belongs_to :organisation serialize :config, JSON def self.get_config(app_name, org_id) SamlAppConfig.find_or_initialize_by( app_name: app_name, organisation_id: org_id ) end end ================================================ FILE: app/models/user.rb ================================================ class User < ApplicationRecord include MsChapAuth devise :timeoutable, :omniauthable, omniauth_providers: [:google_oauth2] has_many :hosts has_many :group_associations has_many :groups, through: :group_associations has_many :group_admin, dependent: :destroy has_one :access_token # TODO: Need to add the validations for the user model, right now a lot of tests fail due to enabling this # validates :first_name, :last_name, :mobile, :user_role, presence: true # validates :first_name, :last_name, format: { with: /[a-zA-Z]/}, allow_blank: true # validates :user_role, inclusion: { in: ENV['USER_ROLES'].split(',') } # validate :validate_email_domain validates :email, uniqueness: true # TODO: Need to enable these again after rails 7 update # validate :remove_default_admin, on: :update # before_save :revoke_admin_when_inactive, on: :update # before_save :set_deactivated_at_when_inactive, on: :update HOME_DIR = '/home'.freeze USER_SHELL = '/bin/bash'.freeze def self.add_user(first_name, last_name, user_role, domain) user = User.new(first_name: first_name, last_name: last_name, user_role: user_role) user.assign_attributes( user_login_id: "#{first_name.downcase}.#{last_name.downcase}", uid: user.generate_uid, email: "#{first_name.downcase}.#{last_name.downcase}@#{domain}", name: "#{first_name} #{last_name}" ) user.save user.initialise_host_and_group if user.persisted? user end def generate_login_id email.split('@').first end def generate_uid(uid_buffer = 5000) uid_buffer = ENV['UID_BUFFER'].present? ? ENV['UID_BUFFER'].to_i : uid_buffer User.last.blank? ? uid_buffer : User.last.id.to_i + uid_buffer end def initialise_host_and_group host = Host.find_or_initialize_by(user: self) unless ENV['DEFAULT_HOST_PATTERN'].blank? host.host_pattern = ENV['DEFAULT_HOST_PATTERN'] end hosts << host groups << Group.find_or_initialize_by(name: user_login_id) end def generate_two_factor_auth(force_create = false) if persisted? && (force_create || (!force_create && auth_key.blank?)) self.auth_key = ROTP::Base32.random_base32 totp = ROTP::TOTP.new(auth_key) self.provisioning_uri = totp.provisioning_uri "GoJek-C #{email}" save! end end def self.create_user(name, email) user = User.find_or_initialize_by(email: email) unless user.persisted? user.assign_attributes( name: name, user_login_id: user.generate_login_id, uid: user.generate_uid ) user.admin = User.first.blank? user.initialise_host_and_group user.save! if user.valid? end user end def self.add_temp_user(name, email) email += "@#{ENV['GATE_HOSTED_DOMAIN']}" user = User.create_user(name, email) user.generate_two_factor_auth user.auth_key end def update_profile(attrs = {}) allowed_keys = %w(public_key name product_name admin active) attrs = attrs.stringify_keys attrs = attrs.select { |k, v| allowed_keys.include?(k) && (v.present? || v.eql?(false)) } assign_attributes(attrs) if active.eql?(false) && deactivated_at.blank? self.deactivated_at = Time.current end save! if valid? end def name_email "#{name} (#{email})" end def self.get_sysadmins user_ids users = User. select(%Q( id, name, uid, user_login_id, ( SELECT gid FROM groups INNER JOIN group_associations ON groups.id = group_associations.group_id WHERE group_associations.user_id = users.id AND groups.name = users.user_login_id LIMIT 1 ) AS gid, ( SELECT COUNT(gid) FROM groups INNER JOIN group_associations ON groups.id = group_associations.group_id WHERE group_associations.user_id = users.id AND groups.name = users.user_login_id LIMIT 1 ) AS gid_count )). where(id: user_ids) users.map(&:user_passwd_response) end def purge! if !self.active self.group_associations.each{ |g| g.destroy } end end def self.includes_restricted_characters? input_string return false if input_string.include?('@') == false restricted_characters = [ ' ', '-', '*'] status = false restricted_characters.each do |char| break if status status = input_string.include?(char) end status end def self.check_email_address email_address !includes_restricted_characters?(email_address) && email_address.split("@").count == 2 ? true : false end def self.valid_domain? domain hosted_domains = ENV['GATE_HOSTED_DOMAINS'].split(',') hosted_domains.include?(domain) end def self.verify params addresses = params[:addresses] return false if addresses.empty? address_array = addresses.split user = User.get_user params[:user] return false if user.blank? return user.permitted_hosts? address_array end def self.authenticate_pam params addresses = params[:addresses] return false if addresses.empty? address_array = addresses.split email, token = get_user_pass_attributes params return false if email.blank? || token.blank? user_auth = find_and_check_user email, token return check_user_host(email, address_array) if user_auth return user_auth end def self.check_user_host email, address_array user = User.get_user email return user.permitted_hosts? address_array end def permitted_vpns? address_array address_array.each do |host_address| Vpn.user_vpns(self).each do |vpn| return true if vpn.ip_address == host_address end end return false end def permitted_hosts? address_array address_array.each do |host_address| host_name = nil begin host_name = Resolv.getname(host_address) rescue Rails.logger.info "Can't resolve name" end host_name = host_address if host_name.blank? hosts.each do |host| return true if /^#{host.host_pattern}/.match(host_name).to_s.present? end end return false end def self.authenticate_cas encoded_string username_password = Base64.decode64 encoded_string.split(" ")[1] username = username_password.split(':').first password = username_password.split(':').last if User.find_and_check_user username, password return username else return nil end end def self.authenticate params email, token = User.get_user_pass_attributes params return false if email.blank? || token.blank? return User.find_and_check_user email, token end def self.get_user user_login_id return User.where(user_login_id: user_login_id, active: true).first end def self.find_and_validate_saml_user(email, password, app_name) query = 'users.email = ? and users.active = ? and groups.name = ?' users = User.joins(:groups).where(query, email, true, app_name) if users.present? users.first.valid_otp?(password) ? users.first : false else false end end def valid_otp?(password) user_key = "#{id}:#{Time.now.hour}" request_count = REDIS_CACHE.incrby user_key, 1 REDIS_CACHE.expire user_key, 3600 return false if request_count > RATE_LIMIT password.eql?(ROTP::TOTP.new(self.auth_key).now) end def self.find_and_check_user email, token user = User.get_user email return false if user.blank? return false if !user.active user_key = "#{user.id}:#{Time.now.hour}" request_count = REDIS_CACHE.incrby user_key, 1 REDIS_CACHE.expire user_key, 3600 return false if request_count > RATE_LIMIT token == ROTP::TOTP.new(user.auth_key).now end def self.get_user_pass_attributes params token = params[:token].present? ? params[:token] : params[:password] email = params[:email].present? ? params[:email] : params[:user] return [nil, nil] if email.blank? || token.blank? [email, token] end def self.get_shadow_name_response name user = User.where(name: name).first return nil if user.blank? user.get_shadow_hash end def get_shadow_hash shadow_hash = {} shadow_hash[:sp_namp] = user_login_id shadow_hash[:sp_pwdp] = "X" shadow_hash[:sp_lstchg] = updated_at.to_i shadow_hash[:sp_min] = 0 shadow_hash[:sp_max] = 99999 shadow_hash[:sp_warn] = 7 shadow_hash[:sp_inact]= nil shadow_hash[:sp_expire] = nil shadow_hash[:sp_flag] = nil shadow_hash end def self.get_all_shadow_response user_array = [] User.all.each do |user| user_array << user.get_shadow_hash end user_array end def self.get_all_passwd_response user_array = [] User.all.each do |user| user_array << user.user_passwd_response end user_array end def self.get_passwd_name_response name user = User.where("email like ?", "#{name}@%").first return [] if user.blank? user.user_passwd_response end def self.response_array response user_response = [] user_response << response user_response end def self.find_active_user_by_email(email) User.where(email: email, active: true).first end def group_names_list self.groups.map(&:name) end def reset_login_limit user_key = "#{self.id}:#{Time.now.hour}" REDIS_CACHE.set user_key, 0 end def within_limits? user_key = "#{self.id}:#{Time.now.hour}" request_count = REDIS_CACHE.incrby user_key, 1 REDIS_CACHE.expire user_key, 3600 request_count < RATE_LIMIT end def self.ms_chap_auth params auth_failed_message = "NT_STATUS_UNSUCCESSFUL: Failure (0xC0000001)" addresses = params[:addresses] user_name = params[:user] challenge_string = params[:challenge] response_string = params[:response] return auth_failed_message if user_name.blank? || challenge_string.blank? || response_string.blank? || addresses.blank? address_array = addresses.split user = User.get_user user_name if user.present? && user.permitted_vpns?(address_array) drift_interval = 30 t = Time.now otps = [] otps.push(user.get_user_otp_at(t)) otps.push(user.get_user_otp_at(t - drift_interval)) otps.push(user.get_user_otp_at(t + drift_interval)) return user.authenticate_ms_chap_with_drift otps, challenge_string, response_string else return auth_failed_message end end #this method is here because we need to mock/stub for testing def get_user_otp return ROTP::TOTP.new(self.auth_key).now end def get_user_otp_at time return ROTP::TOTP.new(self.auth_key).at time end def user_passwd_response user_hash = {} user_hash[:pw_name] = user_login_id user_hash[:pw_passwd] = 'x' user_hash[:pw_uid] = uid.to_i # If gid is supplied (avoid N+1) if respond_to?(:gid) && gid user_hash[:pw_gid] = gid.to_i elsif respond_to?(:gid_count) if gid_count.positive? user_hash[:pw_gid] = groups.where(name: user_login_id).first.gid end elsif groups.where(name: user_login_id).count.positive? user_hash[:pw_gid] = groups.where(name: user_login_id).first.gid end user_hash[:pw_gecos] = name.to_s user_hash[:pw_dir] = "#{HOME_DIR}/#{user_login_id}" user_hash[:pw_shell] = '/bin/bash' user_hash end def group_admin? GroupAdmin.find_by_user_id(self.id).present? end def permitted_endpoint?(endpoint) return false if endpoint.nil? user_groups_id = group_associations.select(:group_id) endpoint. group_endpoints. where('group_id IN (?)', user_groups_id). select(:endpoint_id).present? end private def remove_default_admin admin_users = User.where('active = ? and admin = ? and id <> ?', true, true, id) if (!admin || !active) && admin_users.blank? errors.add(:admin, 'You cannot remove or make inactive the default admin account') end end def validate_email_domain domain_list = ENV['GATE_HOSTED_DOMAINS'].split(',') domain = email.split('@').last errors.add(:email, "Invalid Domain for Email Address") unless domain_list.include?(domain) end def revoke_admin_when_inactive self.admin = false unless active end def set_deactivated_at_when_inactive if active.eql?(false) && deactivated_at.blank? self.deactivated_at = Time.current end end end ================================================ FILE: app/models/vpn.rb ================================================ class Vpn < ApplicationRecord belongs_to :user belongs_to :group has_many :vpn_group_associations has_many :groups, through: :vpn_group_associations has_many :vpn_group_user_associations has_many :users, through: :vpn_group_user_associations has_many :vpn_domain_name_servers has_many :vpn_search_domains has_many :vpn_supplemental_match_domains def self.administrator?(user) vpn_group_ids = VpnGroupAssociation.select(:group_id).collect(&:group_id) managed_group_vpns = user.group_admin.where(group_id: vpn_group_ids) !managed_group_vpns.empty? end def self.managed_vpns(user) vpn_group_ids = VpnGroupAssociation.select(:group_id).collect(&:group_id) managed_group_vpn_ids = user. group_admin. select(:group_id). where(group_id: vpn_group_ids). collect(&:group_id) VpnGroupAssociation.where(group_id: managed_group_vpn_ids).collect(&:vpn).uniq end def self.user_vpns user vpn_group_ids = VpnGroupAssociation.select(:group_id).collect(&:group_id) user_group_vpn_ids = user. group_associations. select(:group_id). where(group_id: vpn_group_ids). collect(&:group_id) VpnGroupAssociation.where(group_id: user_group_vpn_ids).collect(&:vpn).uniq end def migrate_to_new_group group_name = "#{name}_vpn_group".downcase.squish.gsub(' ', '_') group = Group.where(name: group_name).first if group.blank? # create a new group for VPN group = Group.create(name: group_name, description: "#{name} VPN Access group") # Assign VPN to group # get all vpn administrators admins = [] groups.each do |admin_group| admin_group.group_admins.each do |group_admin| group.add_admin group_admin.user end end # get all vpn users # add all vpn users to new group users.each do |user| user.groups << group end group.save! group.vpns << self end end end ================================================ FILE: app/models/vpn_domain_name_server.rb ================================================ class VpnDomainNameServer < ApplicationRecord belongs_to :vpn end ================================================ FILE: app/models/vpn_group_association.rb ================================================ class VpnGroupAssociation < ApplicationRecord belongs_to :vpn belongs_to :group end ================================================ FILE: app/models/vpn_group_user_association.rb ================================================ class VpnGroupUserAssociation < ApplicationRecord belongs_to :vpn belongs_to :user end ================================================ FILE: app/models/vpn_search_domain.rb ================================================ class VpnSearchDomain < ApplicationRecord belongs_to :vpn end ================================================ FILE: app/models/vpn_supplemental_match_domain.rb ================================================ class VpnSupplementalMatchDomain < ApplicationRecord belongs_to :vpn end ================================================ FILE: app/validators/email_validator.rb ================================================ class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value.to_s.match?(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i) record.errors[attribute] << (options[:message] || 'is not an email') end end end ================================================ FILE: app/views/admin/index.html.slim ================================================ h1 hello ================================================ FILE: app/views/api/v1/users/show.json.jbuilder ================================================ json.(@user, :email, :uid, :name, :active, :admin, :home_dir, :shell, :public_key, :user_login_id, :product_name) json.groups @user.groups, :gid, :name ================================================ FILE: app/views/api_resources/_api_resource.json.jbuilder ================================================ json.extract! api_resource, :id, :name, :description, :access_key, :created_at, :updated_at json.url api_resource_url(api_resource, format: :json) ================================================ FILE: app/views/api_resources/_form.html.slim ================================================ = form_for @api_resource, :class => 'form-horizontal', html: {novalidate: 'true'} do |f| - if @api_resource.errors.any? #error_explanation h2 = "#{pluralize(@api_resource.errors.count, "error")} prohibited this api_resource from being saved:" ul - @api_resource.errors.full_messages.each do |message| li = message hr .mb-3 label for="name" | API Name = f.text_field :name, required: true, class: "form-control", placeholder: "API Name" .invalid-feedback .mb-3 label for="description" | Description = f.text_field :description, required: true, class: "form-control", placeholder: "Description" .invalid-feedback .mb-4 = f.submit "Save", class: "btn btn-primary pull-right" ================================================ FILE: app/views/api_resources/edit.html.slim ================================================ h1 Editing api_resource == render 'form' => link_to 'Show', @api_resource '| =< link_to 'Back', api_resources_path ================================================ FILE: app/views/api_resources/index.html.slim ================================================ .container-fluid.col-md-8 - if notice.present? .alert.alert-primary role="alert" #notice = notice h4 API Keys table.table.responsive thead tr th Name th Description th Access key th Owner th Access Group th tbody - @api_resources.each do |api_resource| tr td = api_resource.name td = api_resource.description td = link_to 'Regenerate', regenerate_access_key_api_resource_path(api_resource), data: { confirm: 'Are you sure?' } td = link_to api_resource.user.name, api_resource.user if api_resource.user.present? td = link_to api_resource.group.name, api_resource.group if api_resource.group.present? td = link_to 'Destroy', api_resource, data: { confirm: 'Are you sure?' }, method: :delete br javascript: $("#apiresource-index").addClass("active"); ================================================ FILE: app/views/api_resources/index.json.jbuilder ================================================ json.array! @api_resources, partial: 'api_resources/api_resource', as: :api_resource ================================================ FILE: app/views/api_resources/new.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 - if notice.present? .alert.alert-primary role="alert" #notice = notice br h5 Create new API == render 'form' = link_to 'Back', api_resources_path javascript: $("#apiresource-index").addClass("active"); ================================================ FILE: app/views/api_resources/show.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 - if notice.present? .alert.alert-primary role="alert" #notice = notice br h5 Show API hr .mb-3 label for="name" | API Name p.form-control-static = @api_resource.name .mb-3 label for="description" | Description p.form-control-static = @api_resource.description - if flash[:access_key] .mb-3 label for="access_key" | Access Key .alert.alert-warning role="alert" | Important! please make note of this access key, you will see it only this once. = text_field_tag :access_key, flash[:access_key], class: 'form-control', readonly: "" .mb-4 => link_to 'Edit', edit_api_resource_path(@api_resource) '| => link_to 'Regenerate Access Key', regenerate_access_key_api_resource_path(@api_resource), :data => {:confirm => 'Are you sure?'} '| =< link_to 'Back', api_resources_path ================================================ FILE: app/views/api_resources/show.json.jbuilder ================================================ json.partial! "api_resources/api_resource", api_resource: @api_resource ================================================ FILE: app/views/application/_admin.html.slim ================================================ div ul.navbar-nav li.nav-item.active a.nav-link href="#" Users li.nav-item a.nav-link href="#" Groups ================================================ FILE: app/views/application/_groups_header.html.slim ================================================ #notice = notice - if current_user.admin? = form_tag groups_path, method: 'post' do .row .col-md-8 = text_field_tag "group[name]", "", class: "form-control", autofocus: true .col-md-4 = submit_tag "Add Group", class: "form-control btn btn-sm btn-primary", name: nil br = form_tag groups_path, method: 'get' do .row .col-md-8 = text_field_tag :group_search, params[:group_search], class: "form-control" , autofocus: true .col-md-4 = submit_tag "Search groups", class: "form-control btn btn-sm btn-primary", name: nil hr ================================================ FILE: app/views/application/_host_header.html.slim ================================================ ul.nav.nav-tabs li#host_machine role="presentation" = link_to "MyHosts", host_machines_path li#group role="presentation" = link_to "Groups", groups_path li#host_machine_search role="presentation" a href="#" Search Host li#host_machine_group_search role="presentation" a href="#" Search Host Groups p br ================================================ FILE: app/views/common/errors.json.jbuilder ================================================ json.success false json.errors errors ================================================ FILE: app/views/groups/_form.html.slim ================================================ ================================================ FILE: app/views/groups/index.html.slim ================================================ .container-fluid.col.container-profile - if current_user.admin? = form_tag groups_path, method: 'get', class: "p-2" do .input-group = text_field_tag :group_search, params[:group_search], class: "form-control" , autofocus: true, placeholder: "Search group name..." .input-group-append = submit_tag "Search", class: "button btn btn-secondary", name: nil - if @groups.count > 0 h5 Your managed groups .table-responsive table.table.table-striped thead tr th Name th Gid th Group Admin tbody - @groups.each do |group| tr td = link_to "#{group.name}", group_path(group) td = "#{group.gid}" td - if group.group_admins.present? - group.group_admins.each do |admin| .row = "#{admin.user.try(:name)}" javascript: $("#group-index").addClass("active"); ================================================ FILE: app/views/groups/new.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 h5 Add new group = form_for @group, :class => 'form-horizontal' do |f| - if @group.errors.any? #error_explanation h2 = "#{pluralize(@group.errors.count, "error")} prohibited this vpn from being saved:" ul - @group.errors.full_messages.each do |message| li = message hr .mb-3 label for="name" | Name = f.text_field :name, required: true, class: "form-control", placeholder: "Group Name" .invalid-feedback | Please enter a name .mb-4 = f.submit "Add Group", class: "btn btn-primary pull-right" ================================================ FILE: app/views/groups/show.html.slim ================================================ .container.col-md-8 h5.mb-3 Group Details hr .row .col = "Group Name: #{@group.name}" .col = "Group ID: #{@group.gid}" br - if current_user.admin? or @group.admin?(current_user) = form_tag add_admin_to_group_path(@group.id), method: :post do .row .col | Group Administrator .col - if @group.group_admins.present? - @group.group_admins.each do |admin| .row .col = "#{admin.user.try(:name)}" .col = "#{admin.user.try(:email)}" .col - if (current_user.admin?) = link_to "Remove?", [@group, admin], method: :delete, data: {confirm: "Are you sure to remove #{admin.user.try(:name)} ?"} - if (current_user.admin?) .row .col-8 = text_field_tag "user_id", "", id: "assign_admin_user_id", class: "form-control" .col = submit_tag "Assign admin", class: "form-control btn btn-md btn-primary", disabled: true .row .col-8 = check_box_tag "assign_admin_include_inactive_user", "true", false = " Include Inactive User" .col a name="group_members" br .card .card-body h6.card-title Groups Members (#{@group_users.length}) .table-responsive table.table.table-striped thead tr th User Details th Email Address th Join Date th Expiration Date th tbody - @group_users.sort_by{ |user| user.email}.each do |user| tr td - if user.active? = link_to "#{user.name}", user_path(user) - else = link_to "#{user.name} (inactive)", user_path(user) td = "#{user.email}" td = "#{user.join_date.strftime("%v")}" td = "#{user.group_expiration_date}" td = link_to "Delete", [@group, user], method: :delete, data: {confirm: 'Are you sure to remove this user from the group?'} if current_user.admin or @group.admin?(current_user) br = "*This group does not have any group members" if @group_users.length == 0 br - if current_user.admin or @group.admin?(current_user) .h7 Assign members = form_tag add_user_to_group_path(@group.id), method: :post do .row .col .form_group label | User = text_field_tag "user_id", "", id: "add_user_user_id", class: "form-control" = check_box_tag "add_user_include_inactive_user", "true", false = " Include Inactive User" .col .form_group label | Assignment Expiration date = date_field_tag "expiration_date", "", id: "expiration_date", class: "form-control" | * expiration date is optional (unspecified means permanent) .col br = submit_tag "Add User", class: "form-control btn-md btn-primary", disabled: true br .card .card-body h6.card-title VPN Access .table-responsive table.table.table-striped thead tr th Vpn Details th Vpn name th Vpn Host name th tbody - @group.vpns.sort_by{ |vpn| vpn.name}.uniq{|vpn| vpn.id}.each do |vpn| tr td = link_to "#{vpn.name}", vpn_path(vpn) td = "#{vpn.name}" td = "#{vpn.host_name}" td - if current_user.admin? = link_to "Delete", [@group, vpn], method: :delete, data: {confirm: 'Are you sure to remove this vpn from the group?'} br = "*This group does not have any VPNs associated with it" if @group.vpns.count == 0 br - if current_user.admin? = form_tag add_vpn_to_group_path(@group.id), method: :post do .row .col | Assign VPNs .col .col = text_field_tag "vpn_id", "", id: "add_vpn_vpn_id", class: "form-control" .col = submit_tag "Add Vpn", class: "form-control btn-md btn-primary", disabled: true br .card .card-body h6.card-title Group access hosts .table-responsive table.table.table-striped thead tr th Host Name th tbody - @group.host_machines.each do |machine| tr td = link_to "#{machine.name}", host_machine_path(machine) td = link_to "Delete", [@group, machine], method: :delete, data: {confirm: 'Are you sure to remove this machine from the group??'} if current_user.admin? br = "*This group does not have any host access" if @group.host_machines.count == 0 br hr - if current_user.admin? = form_tag add_machine_to_group_path(@group.id), method: :post do .row .col | Assign Hosts .col .col = text_field_tag "machine_id", "", id: "add_machine_machine_id", class: "form-control" .col = submit_tag "Add Host", class: "form-control btn-md btn-primary", disabled: true javascript: $("#group-index").addClass("active"); ================================================ FILE: app/views/home/index.html.slim ================================================ .form-signin .row .col h1 Gate-SSO br .row .col h3 Single Sign-On br br br br - case ENV['SIGN_IN_TYPE'] - when 'form' = form_tag user_sign_in_path, method: :post do .row .col-sm-10.offset-sm-1 = text_field_tag :name, '', class: 'form-control', placeholder: 'Name', required: true = text_field_tag :email, '', class: 'form-control', placeholder: 'Email', required: true, type: 'email' = submit_tag 'Sign in', class: 'form-control btn-md btn-primary' - else .row .col .col a.btn.btn-block.btn-social.btn-google href="#{url_for user_google_oauth2_omniauth_authorize_path}" span.fa.fa-google | Sign in with Google .col ================================================ FILE: app/views/host_machines/index.html.slim ================================================ .container-fluid.col-md-6.col-md-offset-2 = form_tag host_machines_path, method: 'get', class: "p-2" do .input-group = text_field_tag :host_machine_search, params[:host_machine_search], class: "form-control" , autofocus: true, placeholder: "Search host..." .input-group-append = submit_tag "Search", class: "button btn btn-secondary", name: nil .container-fluid.col-md-6.col-md-offset-2 - if @host_machines.count > 0 .table-responsive table.table.table-striped thead tr th Name tbody - @host_machines.each do |host_machine| tr td = link_to "#{host_machine.name}", host_machine br javascript: $("#hostmachine-index").addClass("active"); ================================================ FILE: app/views/host_machines/new.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 = form_tag host_machines_path, method: 'post' do .input-group = text_field_tag "host_machine[name]", "", class: "form-control", autofocus: true .input-group = submit_tag "Add Host machine", class: "form-control btn btn-sm btn-primary", name: nil ================================================ FILE: app/views/host_machines/show.html.slim ================================================ .container.col-md-8 h5.mb-3 Host Details hr .row .col = @host_machine.name - if current_user.admin? .card .card-body h6.card-title Configurations hr = form_tag "/host_machines/#{@host_machine.id}", method: 'put' do .row .col .custom-control.custom-checkbox = check_box "host_machine", "default_admins", class: "custom-control-input", id: "admin-checkbox" label.custom-control-label for="admin-checkbox" Default Admins? .col = submit_tag "Update", class: "form-control btn-md btn-primary" br .card .card-body .table-responsive table.table.table-striped thead tr th Group Name th tbody - @host_machine.groups.each do |group| tr td = link_to "#{group.name}", group_path(group) td = link_to "Delete", [@machine, group], method: :delete, data: {confirm: 'Are you sure to remove this machine from the group??'} if current_user.admin? br = "*This host does not have any group" if @host_machine.groups.count == 0 br hr - if current_user.admin? = form_tag add_group_to_machine_path, method: :post do .row .col | Assign group .col .col = text_field_tag "group_id", "", class: "form-control" .col = submit_tag "Add group", class: "form-control btn-md btn-primary", disabled: true ================================================ FILE: app/views/layouts/application.html.slim ================================================ doctype html html lang="en" head meta charset="utf-8" meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" meta name="viewport" content="width=device-width, initial-scale=1.0" title= content_for?(:title) ? yield(:title) : "Gate" = csrf_meta_tags = stylesheet_link_tag "application", :media => "all" = stylesheet_link_tag "general", :media => "all" = stylesheet_link_tag "//cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/css/selectize.bootstrap3.min.css" = javascript_include_tag "application" = javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/js/standalone/selectize.min.js" /! Le HTML5 shim, for IE6-8 support of HTML elements /[if lt IE 9] = javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js" body /! Fixed navbar nav.navbar.navbar-expand-md.navbar-dark.fixed-top.bg-dark a.navbar-brand href="/profile" gate button.navbar-toggler aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label=("Toggle navigation") data-target="#main-navigation" data-toggle="collapse" type="button" span.navbar-toggler-icon #main-navigation.collapse.navbar-collapse ul.navbar-nav.mr-auto - if current_user.admin? li#user-index.nav-item = link_to("Users", users_path, class: "nav-link") li#hostmachine-index.nav-item = link_to "Hosts", host_machines_path, class: "nav-link" li#organisation-index.nav-item = link_to "Organisations", organisations_path, class: "nav-link" - if current_user.admin? || current_user.group_admin? li#group-index.nav-item = link_to "Groups", groups_path, class: "nav-link" li#apiresource-index.nav-item = link_to "APIs", api_resources_path, class: "nav-link" li#vpn-index.nav-item = link_to "VPNs", vpns_path, class: "nav-link" li.nav-item.dropdown a#dropdown01.nav-link.dropdown-toggle aria-expanded="false" aria-haspopup="true" data-toggle="dropdown" href="http://example.com" New .dropdown-menu aria-labelledby="dropdown01" = link_to "API", new_api_resource_path, class: "dropdown-item" - if current_user.admin? = link_to "Group", new_group_path, class: "dropdown-item" = link_to "VPN", new_vpn_path, class: "dropdown-item" = link_to "Organisation", new_organisation_path, class: "dropdown-item" = link_to "User", new_user_path, class: "dropdown-item" ul.nav.navbar-nav.navbar-right li.nav-link = link_to "Sign out", users_sign_out_path,:method => :delete , class: "nav-link" li.nav-link.active = link_to current_user.name, user_path(current_user.id), class: "nav-link" /! /.nav-collapse .container-fluid = yield ================================================ FILE: app/views/layouts/home.html.slim ================================================ doctype html html lang="en" head link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css' meta charset="utf-8" meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" meta name="viewport" content="width=device-width, initial-scale=1.0" title= content_for?(:title) ? yield(:title) : "Gate" = csrf_meta_tags = stylesheet_link_tag "application", :media => "all" = favicon_link_tag 'apple-touch-icon-144x144-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '144x144' = favicon_link_tag 'apple-touch-icon-72x72-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '72x72' = favicon_link_tag 'apple-touch-icon-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png' = favicon_link_tag 'favicon.ico', :rel => 'shortcut icon' = javascript_include_tag "application" /! Le HTML5 shim, for IE6-8 support of HTML elements /[if lt IE 9] = javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js" body.home = yield ================================================ FILE: app/views/layouts/profile.html.slim.disabled ================================================ doctype html html lang="en" head meta charset="utf-8" meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" meta name="viewport" content="width=device-width, initial-scale=1.0" title= content_for?(:title) ? yield(:title) : "Gate" = csrf_meta_tags = stylesheet_link_tag "application", :media => "all" = stylesheet_link_tag 'profile' = stylesheet_link_tag "general", :media => "all" = javascript_include_tag "application" /! Le HTML5 shim, for IE6-8 support of HTML elements /[if lt IE 9] = javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js" body.profile_page .container.container-profile .header.clearfix nav ul.nav.nav-pills.pull-right li role="presentation" = link_to "Administration", admin_path if current_user.admin? li role="presentation" = link_to current_user.name, user_path(current_user.id) li.active role="presentation" = link_to "Sign out", users_sign_out_path,:method => :delete h3.text-muted Gate - Single Sign On = yield footer.footer p © 2015 Gate-SSO ================================================ FILE: app/views/nss/add_host.json.jbuilder ================================================ json.success true json.access_key host.access_key json.host host.name json.groups host.groups.map(&:name) ================================================ FILE: app/views/organisations/_form.html.slim ================================================ - if flash.key?(:errors) .alert.alert-danger#organisation_form_errors b Issue creating application br - flash[:errors].each do |msg| = "- #{msg}" br .form-group = f.label :name = f.text_field :name, class: 'form-control' .form-group = f.label :website = f.text_field :website, class: 'form-control' .form-group = f.label :domain = f.text_field :domain, class: 'form-control' .form-group = f.label :country = f.collection_select :country, Country.all.sort_by(&:name), :gec, :name, {}, class: 'form-control' .form-group = f.label :state = f.text_field :state, class: 'form-control' .form-group = f.label :address = f.text_field :address, class: 'form-control' .form-group = f.label :admin_email_address = f.text_field :admin_email_address, class: 'form-control' .form-group = f.label :slug = f.text_field :slug, class: 'form-control' .form-group = f.label :unit_name = f.text_field :unit_name, class: 'form-control' ================================================ FILE: app/views/organisations/config_saml_app.html.slim ================================================ .container.col-md-8 .row.mb-3.mt-2 .col-md-12 h5 Configure #{app_name.titleize} .row.mb-3.mt-2 .col-md-12 = render partial: "organisations/saml_apps/#{app_name}", locals: {org: org, app_name: app_name, saml_config: saml_config, users: users} ================================================ FILE: app/views/organisations/index.html.slim ================================================ .container.col-md-8 .row.mb-3.mt-2 .col-md-8 h5 Organisations .col-md-4.text-right = link_to "New Organisation", new_organisation_path, class: "btn btn-primary", id: 'new_organisation_btn' - if flash.key?(:success) .alert.alert-success#organisation_form_success = flash[:success] - if flash.key?(:errors) .alert.alert-danger#organisation_form_errors = flash[:errors] #organisation_list.table-responsive table.table.table-striped thead tr th Name th URL th Email Domain th Actions tbody - if org_list.present? - org_list.each do |org| tr td = link_to org.name, organisation_path(org) td = link_to org.website, org.website td = org.domain td - if org.saml_setup? .dropdown.position-relative#configureAppDropDownMenuContainer button.btn.btn-primary.btn-sm.dropdown-toggle type="button" id="configureAppDropDownMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-boundary="configureAppDropDownMenuContainer" = "Configure App" .dropdown-menu aria-labelledby="configureAppDropDownMenu" = link_to "Datadog", organisation_config_saml_app_path(organisation_id: org.id, app_name: 'datadog'), class: 'dropdown-item' - else = link_to "Setup SAML", organisation_setup_saml_path(org) - else td.text-center colspan='3' p There are no organisations yet, why don't you create an organisation ================================================ FILE: app/views/organisations/new.html.slim ================================================ .container.col-md-8 .row.mb-3.mt-2 .col-md-12 h5 Create Organisation hr = form_for(org) do |f| = render partial: 'form', locals: {f: f} = f.submit 'Create Organisation', class: 'btn btn-primary mb-2' ================================================ FILE: app/views/organisations/saml_apps/_datadog.html.erb ================================================
Pre Requisites
  1. You need to have a valid account @ datadog.com
  2. You need to be an administrator of the datadog account
Instruction Steps
  1. Configuration Steps on DataDog
  2. Configuration Steps on Gate
  3. Manage Users on Gate
Configuration Steps on Datadog
  1. Login to Datadog as an administrator
  2. Click on in the bottom left, then select Configure SAML

    <%= image_tag("saml_configure_datadog_01.png") %>
  3. Download the IdP Metadata by clicking <%= link_to "here", metadata_path(slug: org.slug, app: 'datadog', download: true) %>
  4. Click Choose file, locate the metadata.xml you just saved, and then click Upload File

    <%= image_tag("saml_configure_datadog_02.png") %>
  5. Click Enable

    <%= image_tag("saml_configure_datadog_03.png") %>
  6. Make a copy of your Single Sign-on URL value
  7. For the below image follow the following steps:

    1. Enable Identity Provider (IdP) Initiated Login
    2. Enable SAML Strict Mode
    3. Note: We will not be using JIT, instead managing users will be done via a different panel
    4. Hit Save
    <%= image_tag("saml_configure_datadog_04.png") %>
Configuration Steps on Gate
  1. Copy the entity Id from the below picture after setting up SAML using the previous Instructions

    <%= image_tag("saml_configure_datadog_03.png") %>
  2. Navigate to Datadog API Setup to setup your API and App Keys
  3. Refer the below image, to add the API Key

    <%= image_tag("saml_configure_datadog_05.png") %>
  4. Refer the below image, to add the App Key

    <%= image_tag("saml_configure_datadog_06.png") %>
  5. Update the SSO Url, Application Key, API Key from the previous steps by refering the below image

    <%= image_tag("saml_configure_datadog_07.png") %>
  6. As a part of this process a group will be auto generated to add and remove users for accessing Datadog. The group created would be called saml_datadog_users
Manage Users on Gate
  1. Click on Manage Users to add/remove users
  2. Enter the email address in the below box to add users to datadog

    <%= image_tag("saml_configure_datadog_08.png") %>
  3. When you click on the page, you will get a list of users with a remove button. You can click remove to remove users from accessing datadog

    <%= image_tag("saml_configure_datadog_09.png") %>
<%= form_for saml_config, url: organisation_save_config_saml_app_path(organisation_id: org.id, app_name: app_name), method: :post do |f| %>
<%= label_tag :datadog_sso_url %> <%= f.text_field :sso_url, class: 'form-control' %>
<%= label_tag :application_key %> <%= text_field_tag 'config[app_key]', saml_config.config['app_key'], class: 'form-control' %>
<%= label_tag :api_key %> <%= text_field_tag 'config[api_key]', saml_config.config['api_key'], class: 'form-control' %>
<%= f.submit 'Update Configuration', class: 'btn btn-primary mb-2'%> <% end %>
<%= form_tag organisation_add_user_saml_app_path(organisation_id: org.id, app_name: app_name), method: :post do %>
<%= label_tag :email_address %> <%= text_field_tag 'email', '', class: 'form-control' %>
<%= submit_tag 'Add User', class: 'btn btn-primary mb-2', id: 'add_user_to_app' %> <% end %>
<% users.each do |user| %> <% end %>
Name Email Actions
<%= link_to user.name, user_path(id: user.id) %> <%= user.email %> <%= link_to "Remove", organisation_remove_user_saml_app_path(organisation_id: org.id, app_name: app_name, email: user.email), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-sm btn-danger', id: "saml_user_remove_#{user.id}" %>
================================================ FILE: app/views/organisations/show.html.slim ================================================ .container.col-md-8 .row.mb-3.mt-2 .col-md-12 h5 Update Organisation hr = form_for(org) do |f| = render partial: 'form', locals: {f: f} = f.submit 'Update Organisation', class: 'btn btn-primary mb-2' ================================================ FILE: app/views/profile/_group_search.html.slim ================================================ .container.container-profile = form_tag profile_group_admin_path, method: 'get' do .row .col-md-8 = text_field_tag :group_search, params[:group_search], class: "form-control" , autofocus: true .col-md-4 = submit_tag "Search groups", class: "form-control btn btn-sm btn-primary", name: nil hr ================================================ FILE: app/views/profile/_user_search.html.slim ================================================ .container.container-profile = form_tag profile_user_admin_path, method: 'get' do .row .col-md-8 = text_field_tag :user_search, params[:user_search], class: "form-control" , autofocus: true .col-md-4 = submit_tag "Search Users", class: "form-control btn btn-sm btn-primary", name: nil hr ================================================ FILE: app/views/profile/group_admin.html.slim ================================================ = render partial: "group_search" ================================================ FILE: app/views/profile/list.html.slim ================================================ = render partial: "user_search" - if @users.count > 0 .table-responsive table.table.table-striped thead tr th Name th Email th Is Active? th Is Admin? tbody - @users.each do |user| tr td = link_to "#{user.name}", user_path(user) td = "#{user.email}" td = "#{user.active.to_s.camelcase}" td = "#{user.admin.to_s.camelcase}" ================================================ FILE: app/views/profile/public_key.html.slim ================================================ ================================================ FILE: app/views/profile/show.html.slim ================================================ .container.container-profile .row.mt-3 .jumbotron .row.marketing .col-lg-6 .row .col-lg-12 strong Use Google Authenticator to scan this QR Code .row.mt-2 .row .col-lg-12 .token-qr - unless @token_qr.blank? = raw @token_qr.as_html .row .col-lg-12 small Note: If you can't see the QR Code, please logout and login again. .col-lg-6 .row .col-lg-12 Or use this token for the authenticator .row.mt-2 .row .col-lg-12 pre h5 #{current_user.auth_key} .row .col-lg-12 a.btn.btn-warning.btn-sm href="/regenerate_authentication" role="button" data-turbolinks="false" (Re)generate Token .row.mt-4 .row .col-lg-12 Download Profiles .row.mt-3 .row .col-lg-12 a.btn.btn-success.btn-sm href="/download_vpn" role="button" data-turbolinks="false" Download OpenVPN profile .row.mt-3 .row .col-lg-12 a.btn.btn-success.btn-sm href="/download_vpn_for_ios_and_mac" role="button" data-turbolinks="false" Download IPSec profile for iOS/Mac .row.mt-4 .row .col-lg-12 Google Authenticator .row .col-lg-12 a href="https://itunes.apple.com/us/app/google-authenticator/id388497605" target="_blank" = image_tag "app_store_en_badge.png", width: 120, alt: "Apple App Store" a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en" target="_blank" = image_tag "google_play_en_badge.png", width: 120, alt: "Google Play Store" .row.marketing .col-lg-6 h4 strong OpenVPN h5 strong Step 1 p | Download OpenVPN client. For Mac, use a href="https://tunnelblick.net/" target="_blank" TunnelBlick. h5 strong Step 2 p Download your OpenVPN profile and install to OpenVPN client. h5 strong Step 3 p Launch OpenVPN client, connect to your profile and enter token code shown on Google Authenticator. .col-lg-6 h4 strong IPSec VPN h5 strong Step 1 p Download your IPSec VPN profile and install by double-clicking on it. h5 strong Step 2 p | Open code System Preferences >> Network | , you will see list of VPNs that you can connect to. h5 strong Step 3 p Connect to any VPN and enter token code shown on Google Authenticator. ================================================ FILE: app/views/profile/user.html.slim ================================================ .container.container-profile .row .col-md-4.text-primary Name .col-md-8 = @user.name .row .col-md-4.text-primary Email .col-md-8 = @user.email .row .col-md-4.text-primary MFA Auth String .col-md-8 = @user.auth_key - if current_user.admin? = form_tag user_update_path, method: 'post' do .row .col-md-4.text-primary Active? .col-md-8 = check_box "user", "active" .row .col-md-4.text-primary Administrator? .col-md-8 = check_box "user", "admin" .row .col-md-4 .col-md-2 = submit_tag "Update", class: "form-control btn-sm btn-primary" hr .row .col-md-4.text-primary = link_to "Groups", groups_path .col-md-8 - @user.groups.each do |group| - unless group.deleted? .row .col-md-8 = "#{group.name} (#{group.gid})" .col-md-4 = link_to "Delete", [@user, group], method: :delete, data: {confirm: 'Are you sure to delete this group?'} if current_user.admin? = "No groups are associated with this user" if @user.groups.count == 0 .row .col-md-4 .col-md-8 .row - if current_user.admin? = form_tag add_group_path, method: :post do .col-sm-8 = select_tag "group_id", options_from_collection_for_select(@group.sort_by {|group| group.name}, "id", "name"), class: "form-control" .col-sm-4 = submit_tag "Add Group", class: "form-control btn-sm btn-primary" hr .row .col-md-4.text-primary Permitted Host Patterns .col-md-8 - @user.hosts.each do |host| - unless host.deleted? .row .col-md-8 = host.host_pattern .col-md-4 = link_to "Delete", [host.user, host], method: :delete, data: {confirm: 'Are you sure to delete this pattern?'} if current_user.admin? = "No host patterns are associated with this user" if @user.hosts.count == 0 .row .col-md-4 .col-md-8 .row - if current_user.admin? = form_tag add_host_path, method: :post do .col-sm-8 = text_field_tag :host_pattern, "", class: "form-control" .col-sm-4 = submit_tag "Add host pattern", class: "form-control btn-sm btn-primary" br hr .row .col-md-4.text-primary Public Key .col-md-8 = form_tag user_public_key_update_path, method: :post do .row .col-sm-12 = text_area_tag "public_key", @user.public_key, class: "form-contol", style: "font-family:monospace;", rows: 15, cols: 35 .row .col-sm-8 .col-sm-4 = submit_tag "Update key", class: "form-control btn-sm btn-primary" hr .row .col-md-4.text-primary = link_to "Vpns", vpns_path .col-md-8 - @user.vpns.each do |vpn| .row .col-md-8 = "#{vpn.name}" hr .row .col-md-4.text-primary VPN Profile .col-md-8 .col-sm-12 .row .col-sm-8 .col-sm-4 a.btn.btn-primary.btn-sm href="/download_vpn/#{@user.id}" role="button" Download VPN Profile br ================================================ FILE: app/views/profile/user_admin.html.slim ================================================ = render partial: "user_search" ================================================ FILE: app/views/saml_idp/idp/new.html.erb ================================================ GoJek – Single Sign on
<%= image_tag("logo.png") %>

Single Sign-on Multifactor Authentication.

<%= form_tag auth_path, class: 'text-left' do %> <%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %> <%= hidden_field_tag("RelayState", params[:RelayState]) %>
<%= label_tag :email %> <%= text_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt form-control" %>
<%= label_tag "Google Authenticator Code" %> <%= password_field_tag :password, params[:password], :autocapitalize => "off", :autocorrect => "off", :spellcheck => "false", :size => 30, :class => "email_pwd txt form-control" %>
<%= submit_tag "Sign in", :class => "button big blueish btn btn-primary" %> <% end %>
================================================ FILE: app/views/users/_search.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 = form_tag users_path, method: 'get', class: "p-2" do .input-group = text_field_tag :user_search, params[:user_search], class: "form-control" , autofocus: true, placeholder: "Search user name..." .input-group-append = submit_tag "Search", class: "button btn btn-secondary", name: nil ================================================ FILE: app/views/users/index.html.slim ================================================ = render partial: "search" - if @users.count > 0 .table-responsive table.table.table-striped thead tr th Name th Email th Is Active? th Is Admin? tbody - @users.each do |user| tr td = link_to "#{user.name}", user_path(user) td = "#{user.email}" td = "#{user.active.to_s.camelcase}" td = "#{user.admin.to_s.camelcase}" javascript: $("#user-index").addClass("active"); ================================================ FILE: app/views/users/new.html.erb ================================================
Create User

<%= form_for(User.new) do |f| %> <% if flash.key?(:errors) %>
Issue Creating User
<%= flash[:errors].map { |msg| "- #{msg}".html_safe }.join("
").html_safe %>
<% end %>
<%= f.label :first_name %> <%= f.text_field :first_name, class: 'form-control' %>
<%= f.label :last_name %> <%= f.text_field :last_name, class: 'form-control' %>
<%= f.label :mobile %> <%= f.text_field :mobile, class: 'form-control' %>
<%= f.label :alternate_email %> <%= f.text_field :alternate_email, class: 'form-control' %>
<%= f.label :user_role %> <%= f.select :user_role, add_placeholder_to_list(roles, "Select A Role"), {}, class: 'form-control' %>
<%= label_tag :domain %> <%= select_tag :user_domain, options_for_select(add_placeholder_to_list(domains, "Select A Domain", string_convert: '')), class: 'form-control' %>
<%= f.submit 'Create User', class: 'btn btn-primary mb-2' %> <% end %>
================================================ FILE: app/views/users/show.html.slim ================================================ .container.col-md-8 h5.mb-3 User Profile hr - if flash.key?(:success) .alert.alert-success = flash[:success] form.needs-validation novalidate="" .form-row.mb-3 .col label for="Name" b = @user.name .col .input-group .input-group-prepend span.input-group-text @ input#email.form-control placeholder="you@example.com" readonly="" type="email" value="#{@user.email}" - if (current_user.admin? or (current_user.id == @user.id)) form.needs-validation novalidate="" .form-row.mb-3 .col b Your Access Token .col - if flash[:token] .alert.alert-warning role="alert" | Important! please make note of this token, you will see it only this once. pre = flash[:token] - else = link_to 'Regenerate Token', regenerate_token_user_path(@user), :data => {:confirm => 'Are you sure?'} - if current_user.admin? .card .card-body h6.card-title Access and Permissions hr = form_tag user_update_path, method: 'post' do .row .col .custom-control.custom-checkbox = check_box "user", "active", class: "custom-control-input", id: "active-checkbox" label.custom-control-label for="active-checkbox" Active? .col .custom-control.custom-checkbox = check_box "user", "admin", class: "custom-control-input", id: "admin-checkbox" label.custom-control-label for="admin-checkbox" Adminstrator? .col = submit_tag "Update", class: "form-control btn-md btn-primary" br .card .card-body h6.card-title Groups hr .table-responsive table.table.table-striped thead tr th Group Name th Expiration Date th Action tbody - @user_groups.each do |group| - unless group.deleted? tr td = "#{group.name} (#{group.gid})" td = "#{group.group_expiration_date}" td = link_to "Remove group", [@user, group], method: :delete, data: {confirm: 'Are you sure to delete this group?'} if current_user.admin? br - if current_user.admin? = form_tag add_group_path, method: :post do .row .col = text_field_tag "group_id", "", class: "form-control" .col .row .col = submit_tag "Add Group", class: "form-control btn-md btn-primary pull-right", disabled: true .col = "No groups are associated with this user" if @user.groups.count == 0 br .card .card-body - if (current_user.admin? or (current_user.id == @user.id)) h6.card-title Public Key = form_tag user_public_key_update_path, method: :post do .row .col = text_area_tag "public_key", @user.public_key, class: "form-contol text-align-top", style: "height:200px;width:100%;", placeholder: "Your public key here..." br .row .col-md-3 = submit_tag "Update key", class: "form-control btn-md btn-primary" - else h6.card-title Public Key .row .col = @user.public_key br .card .card-body h6.card-title VPN Access table.table.responsive thead tr th="Name" th="Hostname" th="IP Address" th tbody - @vpns.each do |vpn| tr td=link_to vpn.name, vpn td=vpn.host_name td=vpn.ip_address = "No vpns are associated with this user" if @vpns.count == 0 - if current_user.admin? br .card .card-body h6.card-title SAML Product Access hr - if @user.product_name.nil? = form_tag user_path, method: :patch do .col-md-6 = select_tag :product_name, options_for_select(ENV['PRODUCT_LIST'].split(",")), class: "form-control" .col-md-2 = submit_tag "Assign", class: "form-control btn-md btn-primary" - else .row .col-md-4.text-primary .col-md-6 = @user.product_name br .col-md-4 .col-md-8 .row = form_tag user_path, method: :patch do .col-sm-8 = select_tag :product_name, options_for_select(ENV['PRODUCT_LIST'].split(",")), class: "form-control" .col-sm-4 = submit_tag "Modify", class: "form-control btn-md btn-primary" javascript: $("#user-index").addClass("active"); ================================================ FILE: app/views/vpns/_form.html.slim ================================================ = form_for @vpn, :class => 'form-horizontal' do |f| - if @vpn.errors.any? #error_explanation h2 = "#{pluralize(@vpn.errors.count, "error")} prohibited this vpn from being saved:" ul - @vpn.errors.full_messages.each do |message| li = message hr .mb-3 label for="name" | Name = f.text_field :name, required: true, class: "form-control", placeholder: "VPN Name" .mb-3 label for="host_name" | Host name = f.text_field :host_name, required: true, class: "form-control", placeholder: "Host name" .invalid-feedback | Please enter host name. .mb-3 label for="ip_address" | IP Address = f.text_field :ip_address, required: true, class: 'form-control', placeholder: "IP address" .invalid-feedback | Please enter IP address. .mb-4 = f.submit "Save", class: "btn btn-primary pull-right" ================================================ FILE: app/views/vpns/edit.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 - if notice.present? .alert.alert-primary role="alert" #notice = notice br h5 Edit VPN Details = render "form" javascript: $("#vpn-index").addClass("active"); ================================================ FILE: app/views/vpns/index.html.slim ================================================ .container-fluid.col-md-8.col-md-offset-4 - if notice.present? .alert.alert-primary role="alert" #notice = notice - if Vpn.administrator? current_user h5 Managed VPNs table.table.responsive thead tr th="Name" th="Hostname" th="IP Address" - Vpn.managed_vpns(current_user).each do |vpn| tbody tr td= link_to vpn.name, vpn td= vpn.host_name td= vpn.ip_address h5 b Listing VPNs table.table.responsive thead tr th="Name" th="Hostname" th="IP Address" th tbody - @vpns.each do |vpn| tr td= link_to vpn.name, vpn td= vpn.host_name td= vpn.ip_address td= link_to "Delete", vpn, method: :delete, data: {confirm: 'Are you sure to remove this vpn from gate?'} if current_user.admin? javascript: $("#vpn-index").addClass("active"); ================================================ FILE: app/views/vpns/new.html.slim ================================================ .container-fluid.col-md-4.col-md-offset-4 - if notice.present? .alert.alert-primary role="alert" #notice = notice br h5 Add new VPN = render "form" javascript: $("#vpn-index").addClass("active"); ================================================ FILE: app/views/vpns/show.html.slim ================================================ .container.container-profile - if notice.present? .alert.alert-primary role="alert" #notice = notice br .row .col h5.mb-3 = @vpn.name .col .col = link_to "Manage this vpn", @vpn.groups.first if @vpn.groups.count > 0 hr .row .col label for="VPN Host : " b = @vpn.host_name .col label for="VPN Host IP : " b = @vpn.ip_address .col = link_to "Edit VPN Details", edit_vpn_path, class: "form-control btn-sm btn-primary" if current_user.admin? br a name="dns_hosts" .card .card-body h6.card-title DNS Hosts hr - @vpn.vpn_domain_name_servers.each do |dns_server| .row .col = "#{dns_server.server_address}" .col.append = link_to "Remove server", remove_dns_from_vpn_path(@vpn, dns_server), method: :delete, data: {confirm: 'Are you sure to delete this DNS host?'} if current_user.admin? br = "*This VPN does not have any domain name servers associated with it" if @vpn.vpn_domain_name_servers.count == 0 br hr - if current_user.admin? = form_tag add_dns_to_vpn_path, method: :post do .row .col = text_field_tag :server_address, "", class: "form-control", placeholder: 'dns server...', required: true .col = submit_tag "Add DNS Server", class: "form-control btn-md btn-primary" br a name="search_domains" .card .card-body h6.card-title Search Domains hr - @vpn.vpn_search_domains.each do |search_domain| .row .col = "#{search_domain.search_domain}" .col.append = link_to "Remove search domain", remove_search_domain_from_vpn_path(@vpn, search_domain), method: :delete, data: {confirm: 'Are you sure to delete this search domain string?'} if current_user.admin? br = "*This VPN does not have any search domain strings" if @vpn.vpn_search_domains.count == 0 br hr - if current_user.admin? = form_tag add_search_domain_to_vpn_path, method: :post do .row .col = text_field_tag :search_domain, "", class: "form-control", placeholder: 'dns search string...', required: true .col = submit_tag "Add search domain", class: "form-control btn-md btn-primary" br a name="match_domains" .card .card-body h6.card-title Supplemental match domain hr - @vpn.vpn_supplemental_match_domains.each do |match_domain| .row .col = "#{match_domain.supplemental_match_domain}" .col.append = link_to "Remove supplemental search domain", remove_supplemental_match_domain_from_vpn_path(@vpn, match_domain), method: :delete, data: {confirm: 'Are you sure to delete this Supplemental match domain?'} if current_user.admin? br = "*This VPN does not have any search domain strings" if @vpn.vpn_search_domains.count == 0 br hr - if current_user.admin? = form_tag add_supplemental_match_domain_to_vpn_path, method: :post do .row .col = text_field_tag :supplemental_match_domain, "", class: "form-control", placeholder: 'Supplemental match domain...', required: true .col = submit_tag "Add Supplemental match domain", class: "form-control btn-md btn-primary" br .card .card-body .row .col h6.card-title VPN Access Group: - @vpn.groups.each do |group| - unless group.deleted? .col = link_to group.name, group = "No groups are associated with this VPN, Goto Groups page to add VPN" if @vpn.groups.count == 0 javascript: $("#vpn-index").addClass("active"); ================================================ FILE: bin/bundle ================================================ #!/usr/bin/env ruby ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) load Gem.bin_path('bundler', 'bundle') ================================================ FILE: bin/rails ================================================ #!/usr/bin/env ruby APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../config/boot" require "rails/commands" ================================================ FILE: bin/rake ================================================ #!/usr/bin/env ruby require_relative "../config/boot" require "rake" Rake.application.run ================================================ FILE: bin/setup ================================================ #!/usr/bin/env ruby require "fileutils" # path to your application root. APP_ROOT = File.expand_path("..", __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end FileUtils.chdir APP_ROOT do # This script is a way to set up or update your development environment automatically. # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. puts "== Installing dependencies ==" system! "gem install bundler --conservative" system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") # FileUtils.cp "config/database.yml.sample", "config/database.yml" # end puts "\n== Preparing database ==" system! "bin/rails db:prepare" puts "\n== Removing old logs and tempfiles ==" system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" system! "bin/rails restart" end ================================================ FILE: bin/update ================================================ #!/usr/bin/env ruby require 'pathname' require 'fileutils' include FileUtils # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end chdir APP_ROOT do # This script is a way to update your development environment automatically. # Add necessary update steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') puts "\n== Updating database ==" system! 'bin/rails db:migrate' puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" system! 'bin/rails restart' end ================================================ FILE: config/application.rb ================================================ require_relative "boot" require "rails/all" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Gate class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.0 # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files # in config/environments, which are processed later. # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") end end ================================================ FILE: config/boot.rb ================================================ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) require "bundler/setup" # Set up gems listed in the Gemfile. ================================================ FILE: config/cable.yml ================================================ development: adapter: async test: adapter: async production: adapter: redis url: redis://localhost:6379/1 ================================================ FILE: config/database.yml ================================================ default: &default adapter: mysql2 pool: 5 timeout: 5000 host: <%= ENV['GATE_DB_HOST'] %> port: <%= ENV['GATE_DB_PORT'] %> username: <%= ENV['GATE_DB_USER'] %> password: <%= ENV['GATE_DB_PASSWORD'] %> development: <<: *default database: gate_development host: localhost post: 3306 username: gate_development password: password properties: useSSL: false test: <<: *default database: gate_test host: localhost post: 3306 username: gate_test password: password properties: useSSL: false integration: <<: *default database: <%= ENV['GATE_DB_NAME'] %> production: <<: *default pool: 16 database: <%= ENV['GATE_DB_NAME'] %> ================================================ FILE: config/environment.rb ================================================ # Load the Rails application. require_relative "application" # Initialize the Rails application. Rails.application.initialize! ================================================ FILE: config/environments/development.rb ================================================ require "active_support/core_ext/integer/time" Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded any time # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports. config.consider_all_requests_local = true # Enable server timing config.server_timing = true # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false config.cache_store = :null_store end # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raise exceptions for disallowed deprecations. config.active_support.disallowed_deprecation = :raise # Tell Active Support which deprecation messages to disallow. config.active_support.disallowed_deprecation_warnings = [] # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true # Suppress logger output for asset requests. config.assets.quiet = true # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true # Uncomment if you wish to allow Action Cable access from any origin. # config.action_cable.disable_request_forgery_protection = true config.legacy_connection_handling = false end ================================================ FILE: config/environments/integration.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like # NGINX, varnish or squid. # config.action_dispatch.rack_cache = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'] == 'true' # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Asset digests allow you to set far-future HTTP expiration dates on all assets, # yet still be able to expire them through the digest params. config.assets.digest = true # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. config.logger = Logger.new(STDOUT) config.log_level = :DEBUG # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production. # config.cache_store = :mem_cache_store # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end ================================================ FILE: config/environments/production.rb ================================================ require "active_support/core_ext/integer/time" Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). # config.require_master_key = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? # Compress CSS using a preprocessor. # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil # config.action_cable.url = "wss://example.com/cable" # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Include generic and useful information about system operation, but avoid logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). config.log_level = :info # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment). # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "gate_production" config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Don't log any deprecations. config.active_support.report_deprecations = false # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Use a different logger for distributed setups. # require "syslog/logger" # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false config.legacy_connection_handling = false end ================================================ FILE: config/environments/test.rb ================================================ require "active_support/core_ext/integer/time" # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Turn false under Spring and add config.action_view.cache_template_loading = true. config.cache_classes = true # Eager loading loads your whole application. When running a single test locally, # this probably isn't necessary. It's a good idea to do in a continuous integration # system, or in some way before deploying your code. config.eager_load = ENV["CI"].present? # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false config.cache_store = :null_store # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Store uploaded files on the local file system in a temporary directory. config.active_storage.service = :test config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raise exceptions for disallowed deprecations. config.active_support.disallowed_deprecation = :raise # Tell Active Support which deprecation messages to disallow. config.active_support.disallowed_deprecation_warnings = [] # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true config.active_record.legacy_connection_handling = false end ================================================ FILE: config/initializers/application_controller_renderer.rb ================================================ # Be sure to restart your server when you modify this file. # ApplicationController.renderer.defaults.merge!( # http_host: 'example.org', # https: false # ) ================================================ FILE: config/initializers/assets.rb ================================================ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = "1.0" # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. # Rails.application.config.assets.precompile += %w( admin.js admin.css ) ================================================ FILE: config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: config/initializers/content_security_policy.rb ================================================ # Be sure to restart your server when you modify this file. # Define an application-wide content security policy. # See the Securing Rails Applications Guide for more information: # https://guides.rubyonrails.org/security.html#content-security-policy-header # Rails.application.configure do # config.content_security_policy do |policy| # policy.default_src :self, :https # policy.font_src :self, :https, :data # policy.img_src :self, :https, :data # policy.object_src :none # policy.script_src :self, :https # policy.style_src :self, :https # # Specify URI for violation reports # # policy.report_uri "/csp-violation-report-endpoint" # end # # # Generate session nonces for permitted importmap and inline scripts # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # config.content_security_policy_nonce_directives = %w(script-src) # # # Report violations without enforcing the policy. # # config.content_security_policy_report_only = true # end ================================================ FILE: config/initializers/cookies_serializer.rb ================================================ # Be sure to restart your server when you modify this file. # Specify a serializer for the signed and encrypted cookie jars. # Valid options are :json, :marshal, and :hybrid. Rails.application.config.action_dispatch.cookies_serializer = :json ================================================ FILE: config/initializers/devise.rb ================================================ # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. Devise.setup do |config| # The secret key used by Devise. Devise uses this key to generate # random tokens. Changing this key will render invalid all existing # confirmation, reset password and unlock tokens in the database. # Devise will use the `secret_key_base` as its `secret_key` # by default. You can change it below and use your own secret key. # config.secret_key = 'ffd38086f1b74e9ba7fe5876d3536949b4c1e93a50f3ad16f0c50dc1ecb48262c350f819a83ac3482ea51f6aefbd4aa3cd52cd9b6c666ca5f8cbb6f29d760aae' config.stretches = Rails.env.test? ? 1 : 10 # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class # with default "from" parameter. config.omniauth :google_oauth2, ENV['GATE_OAUTH_CLIENT_ID'], ENV['GATE_OAUTH_CLIENT_SECRET'], { hd: ENV['GATE_HOSTED_DOMAINS'].split(','), site: ENV['GATE_SERVER_URL'] } config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' config.secret_key = ENV['GATE_CONFIG_SECRET'] # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' # Configure the parent class responsible to send e-mails. # config.parent_mailer = 'ActionMailer::Base' # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and # :mongoid (bson_ext recommended) by default. Other ORMs may be # available as additional gems. require 'devise/orm/active_record' # ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is # just :email. You can configure it to use [:username, :subdomain], so for # authenticating a user, both parameters are required. Remember that those # parameters are used only when authenticating and not when retrieving from # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. # config.authentication_keys = [:email] # Configure parameters from the request object used for authentication. Each entry # given should be a request method and it will automatically be passed to the # find_for_authentication method and considered in your model lookup. For instance, # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. # The same considerations mentioned for authentication_keys also apply to request_keys. # config.request_keys = [] # Configure which authentication keys should be case-insensitive. # These keys will be downcased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. config.case_insensitive_keys = [:email] # Configure which authentication keys should have whitespace stripped. # These keys will have whitespace before and after removed upon creating or # modifying a user and when used to authenticate or find a user. Default is :email. config.strip_whitespace_keys = [:email] # Tell if authentication through request.params is enabled. True by default. # It can be set to an array that will enable params authentication only for the # given strategies, for example, `config.params_authenticatable = [:database]` will # enable it only for database (email + password) authentication. # config.params_authenticatable = true # Tell if authentication through HTTP Auth is enabled. False by default. # It can be set to an array that will enable http authentication only for the # given strategies, for example, `config.http_authenticatable = [:database]` will # enable it only for database authentication. The supported strategies are: # :database = Support basic authentication with authentication key + password # config.http_authenticatable = false # If 401 status code should be returned for AJAX requests. True by default. # config.http_authenticatable_on_xhr = true # The realm used in Http Basic Authentication. 'Application' by default. # config.http_authentication_realm = 'Application' # It will change confirmation, password recovery and other workflows # to behave the same regardless if the e-mail provided was right or wrong. # Does not affect registerable. # config.paranoid = true # By default Devise will store the user in session. You can skip storage for # particular strategies by setting this option. # Notice that if you are skipping storage for all authentication paths, you # may want to disable generating routes to Devise's sessions controller by # passing skip: :sessions to `devise_for` in your config/routes.rb config.skip_session_storage = [:http_auth] # By default, Devise cleans up the CSRF token on authentication to # avoid CSRF token fixation attacks. This means that, when using AJAX # requests for sign in and sign up, you need to get a new CSRF token # from the server. You can disable this option at your own risk. # config.clean_up_csrf_token_on_authentication = true # ==> Configuration for :database_authenticatable # For bcrypt, this is the cost for hashing the password and defaults to 11. If # using other algorithms, it sets how many times you want the password to be hashed. # # Limiting the stretches to just one in testing will increase the performance of # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use # a value less than 10 in other environments. Note that, for bcrypt (the default # algorithm), the cost increases exponentially with the number of stretches (e.g. # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). config.stretches = Rails.env.test? ? 1 : 11 # Set up a pepper to generate the hashed password. # config.pepper = '16f08c87da9835ae3ac2cc84ba3ce339d6820771cfb4dfdd79360036bbace40ee9b74a691f26aa2eeb17cbc449cfda195e09874a792df258a2abe9d696f4a98b' # Send a notification email when the user's password is changed # config.send_password_change_notification = false # ==> Configuration for :confirmable # A period that the user is allowed to access the website even without # confirming their account. For instance, if set to 2.days, the user will be # able to access the website for two days without confirming their account, # access will be blocked just in the third day. Default is 0.days, meaning # the user cannot access the website without confirming their account. # config.allow_unconfirmed_access_for = 2.days # A period that the user is allowed to confirm their account before their # token becomes invalid. For example, if set to 3.days, the user can confirm # their account within 3 days after the mail was sent, but on the fourth day # their account can't be confirmed with the token any more. # Default is nil, meaning there is no restriction on how long a user can take # before confirming their account. # config.confirm_within = 3.days # If true, requires any email changes to be confirmed (exactly the same way as # initial account confirmation) to be applied. Requires additional unconfirmed_email # db field (see migrations). Until confirmed, new email is stored in # unconfirmed_email column, and copied to email column on successful confirmation. config.reconfirmable = true # Defines which key will be used when confirming an account # config.confirmation_keys = [:email] # ==> Configuration for :rememberable # The time the user will be remembered without asking for credentials again. # config.remember_for = 2.weeks # Invalidates all the remember me tokens when the user signs out. config.expire_all_remember_me_on_sign_out = true # If true, extends the user's remember period when remembered via cookie. # config.extend_remember_period = false # Options to be passed to the created cookie. For instance, you can set # secure: true in order to force SSL only cookies. # config.rememberable_options = {} # ==> Configuration for :validatable # Range for password length. config.password_length = 6..128 # Email regex used to validate email formats. It simply asserts that # one (and only one) @ exists in the given string. This is mainly # to give user feedback and not to assert the e-mail validity. config.email_regexp = /\A[^@]+@[^@]+\z/ # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this # time the user will be asked for credentials again. Default is 30 minutes. # config.timeout_in = 30.minutes # ==> Configuration for :lockable # Defines which strategy will be used to lock an account. # :failed_attempts = Locks an account after a number of failed attempts to sign in. # :none = No lock strategy. You should handle locking by yourself. # config.lock_strategy = :failed_attempts # Defines which key will be used when locking and unlocking an account # config.unlock_keys = [:email] # Defines which strategy will be used to unlock an account. # :email = Sends an unlock link to the user email # :time = Re-enables login after a certain amount of time (see :unlock_in below) # :both = Enables both strategies # :none = No unlock strategy. You should handle unlocking by yourself. # config.unlock_strategy = :both # Number of authentication tries before locking an account if lock_strategy # is failed attempts. # config.maximum_attempts = 20 # Time interval to unlock the account if :time is enabled as unlock_strategy. # config.unlock_in = 1.hour # Warn on the last attempt before the account is locked. # config.last_attempt_warning = true # ==> Configuration for :recoverable # # Defines which key will be used when recovering the password for an account # config.reset_password_keys = [:email] # Time interval you can reset your password with a reset password key. # Don't put a too small interval or your users won't have the time to # change their passwords. config.reset_password_within = 6.hours # When set to false, does not sign a user in automatically after their password is # reset. Defaults to true, so a user is signed in automatically after a reset. # config.sign_in_after_reset_password = true # ==> Configuration for :encryptable # Allow you to use another hashing or encryption algorithm besides bcrypt (default). # You can use :sha1, :sha512 or algorithms from others authentication tools as # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 # for default behavior) and :restful_authentication_sha1 (then you should set # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). # # Require the `devise-encryptable` gem when using anything other than bcrypt # config.encryptor = :sha512 # ==> Scopes configuration # Turn scoped views on. Before rendering "sessions/new", it will first check for # "users/sessions/new". It's turned off by default because it's slower if you # are using only default views. # config.scoped_views = false # Configure the default scope given to Warden. By default it's the first # devise role declared in your routes (usually :user). # config.default_scope = :user # Set this configuration to false if you want /users/sign_out to sign out # only the current scope. By default, Devise signs out all scopes. # config.sign_out_all_scopes = true # ==> Navigation configuration # Lists the formats that should be treated as navigational. Formats like # :html, should redirect to the sign in page when the user does not have # access, but formats like :xml or :json, should return 401. # # If you have any extra navigational formats, like :iphone or :mobile, you # should add them to the navigational formats lists. # # The "*/*" below is required to match Internet Explorer requests. # config.navigational_formats = ['*/*', :html] # The default HTTP method used to sign out a resource. Default is :delete. config.sign_out_via = :delete # ==> OmniAuth # Add a new OmniAuth provider. Check the wiki for more information on setting # up on your models and hooks. # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or # change the failure app, you can configure them inside the config.warden block. # # config.warden do |manager| # manager.intercept_401 = false # manager.default_strategies(scope: :user).unshift :some_external_strategy # end # ==> Mountable engine configurations # When using Devise inside an engine, let's call it `MyEngine`, and this engine # is mountable, there are some extra configurations to be taken into account. # The following options are available, assuming the engine is mounted as: # # mount MyEngine, at: '/my_engine' # # The router that invoked `devise_for`, in the example above, would be: # config.router_name = :my_engine # # When using OmniAuth, Devise cannot automatically set OmniAuth path, # so you need to do it manually. For the users scope, it would be: # config.omniauth_path_prefix = '/my_engine/users/auth' end ================================================ FILE: config/initializers/dotenv.rb ================================================ begin Dotenv.require_keys('GATE_DB_HOST', 'GATE_DB_PORT', 'GATE_DB_USER', 'GATE_DB_PASSWORD', 'CACHE_HOST', 'CACHE_PORT', 'GATE_HOSTED_DOMAINS', 'GATE_HOSTED_DOMAIN') rescue => exception puts exception.to_s exit(-1) end ================================================ FILE: config/initializers/filter_parameter_logging.rb ================================================ # Be sure to restart your server when you modify this file. # Configure parameters to be filtered from the log file. Use this to limit dissemination of # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported # notations and behaviors. Rails.application.config.filter_parameters += [ :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn ] ================================================ FILE: config/initializers/inflections.rb ================================================ # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, "\\1en" # inflect.singular /^(ox)en/i, "\\1" # inflect.irregular "person", "people" # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym "RESTful" # end ================================================ FILE: config/initializers/mime_types.rb ================================================ # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf ================================================ FILE: config/initializers/new_framework_defaults.rb ================================================ # Be sure to restart your server when you modify this file. # # This file contains migration options to ease your Rails 5.0 upgrade. # # Once upgraded flip defaults one by one to migrate to the new default. # # Read the Rails 5.0 release notes for more info on each option. # Enable per-form CSRF tokens. Previous versions had false. #Rails.application.config.action_controller.per_form_csrf_tokens = false # Enable origin-checking CSRF mitigation. Previous versions had false. #Rails.application.config.action_controller.forgery_protection_origin_check = false # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. # Previous versions had false. #ActiveSupport.to_time_preserves_timezone = false # Require `belongs_to` associations by default. Previous versions had false. #Rails.application.config.active_record.belongs_to_required_by_default = false ================================================ FILE: config/initializers/new_framework_defaults_7_0.rb ================================================ # Be sure to restart your server when you modify this file. # # This file eases your Rails 7.0 framework defaults upgrade. # # Uncomment each configuration one by one to switch to the new default. # Once your application is ready to run with all new defaults, you can remove # this file and set the `config.load_defaults` to `7.0`. # # Read the Guide for Upgrading Ruby on Rails for more info on each option. # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html # `button_to` view helper will render `