├── .env ├── .gitignore ├── .rubocop-disabled.yml ├── .rubocop-enforced.yml ├── .rubocop.yml ├── .ruby-version ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── javascripts │ │ ├── application.js │ │ ├── cable.js │ │ └── welcome.js │ └── stylesheets │ │ ├── application.scss │ │ ├── buttons.scss │ │ └── nav_menu.scss ├── controllers │ ├── application_controller.rb │ ├── matter_controller.rb │ ├── profile_controller.rb │ └── welcome_controller.rb ├── helpers │ ├── identity_authentication_helper.rb │ └── manage_authorization_helper.rb ├── models │ └── application_record.rb └── views │ ├── application │ └── error.html.erb │ ├── layouts │ └── application.html.erb │ ├── matter │ ├── index.html.erb │ └── show.html.erb │ ├── profile │ └── index.html.erb │ └── welcome │ └── index.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── environment.rb ├── environments │ ├── development.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── permissions_policy.rb │ ├── secure_headers.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── secrets.yml ├── spring.rb └── storage.yml ├── docker-compose.yml ├── docs ├── docker_setup.md └── manual_setup.md ├── overlord.yml ├── public ├── 404.html ├── 422.html ├── 500.html ├── clio-logo-black.svg ├── clio-logo-blue.svg ├── clio-logo-white.svg ├── favicon.ico ├── robots.txt └── switchboard_logo.svg ├── scripts ├── entrypoint.sh └── resolve_docker_host.rb └── test ├── controllers └── welcome_controller_test.rb └── test_helper.rb /.env: -------------------------------------------------------------------------------- 1 | # Make sure you keep your secrets safe! How you do this is up to you, but in general: 2 | # * Never commit unencrypted secrets 3 | # * Don't share secrets in unencrypted messaging systems 4 | # * Store secrets safely 5 | 6 | CLIO_MANAGE_CLIENT_ID=YOUR_MANAGE_APP_CLIENT_ID 7 | CLIO_MANAGE_CLIENT_SECRET=YOUR_MANAGE_APP_CLIENT_SECRET 8 | CLIO_MANAGE_SITE_URL=https://app.clio.com/ 9 | 10 | CLIO_IDENTITY_CLIENT_ID=YOUR_IDENTITY_APP_CLIENT_ID 11 | CLIO_IDENTITY_CLIENT_SECRET=YOUR_IDENTITY_APP_CLIENT_SECRET 12 | CLIO_IDENTITY_SITE_URL=https://account.clio.com/ 13 | 14 | ROOT_URL=http://localhost:3013/ 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | capybara-*.html 3 | .rspec 4 | /db/*.sqlite3 5 | /db/*.sqlite3-journal 6 | /db/*.sqlite3-[0-9]* 7 | /public/system 8 | /coverage/ 9 | /spec/tmp 10 | *.orig 11 | rerun.txt 12 | pickle-email-*.html 13 | /.idea/* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Comment out this rule if you are OK with secrets being uploaded to the repo 22 | config/initializers/secret_token.rb 23 | config/master.key 24 | 25 | # Only include if you have production secrets in this file, which is no longer a Rails default 26 | # config/secrets.yml 27 | 28 | # dotenv, dotenv-rails 29 | # Comment out these rules if environment variables can be committed 30 | .env 31 | .env.* 32 | 33 | ## Environment normalization: 34 | 35 | # these should all be checked in to normalize the environment: 36 | # Gemfile.lock, .ruby-version, .ruby-gemset 37 | 38 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 39 | .rvmrc 40 | 41 | # if using bower-rails ignore default bower_components path bower.json files 42 | /vendor/assets/bower_components 43 | *.bowerrc 44 | bower.json 45 | 46 | # Ignore pow environment settings 47 | .powenv 48 | 49 | # Ignore Byebug command history file. 50 | .byebug_history 51 | 52 | # Ignore node_modules 53 | node_modules/ 54 | 55 | # Ignore precompiled javascript packs 56 | /public/packs 57 | /public/packs-test 58 | /public/assets 59 | 60 | # Ignore yarn files 61 | /yarn-error.log 62 | yarn-debug.log* 63 | .yarn-integrity 64 | 65 | # Ignore uploaded files in development 66 | /storage/* 67 | !/storage/.keep 68 | /public/uploads 69 | 70 | .DS_Store 71 | -------------------------------------------------------------------------------- /.rubocop-disabled.yml: -------------------------------------------------------------------------------- 1 | 2 | # These are all the cops that are disabled in the default configuration. 3 | 4 | # By default, the rails cops are not run. Override in project or home 5 | # directory .rubocop.yml files, or by giving the -R/--rails option. 6 | 7 | ################# Layout ################# 8 | 9 | Layout/BlockAlignment: 10 | Description: 'Align block ends correctly.' 11 | Enabled: false 12 | 13 | Layout/DefEndAlignment: 14 | Description: 'Align ends corresponding to defs correctly.' 15 | Enabled: false 16 | 17 | Layout/ElseAlignment: 18 | Description: 'Align elses and elsifs correctly.' 19 | Enabled: false 20 | 21 | Layout/EmptyLinesAroundAccessModifier: 22 | Description: "Keep blank lines around access modifiers." 23 | Enabled: false 24 | 25 | Layout/EmptyLinesAroundBlockBody: 26 | Description: "Keeps track of empty lines around block bodies." 27 | Enabled: false 28 | 29 | Layout/EmptyLinesAroundClassBody: 30 | Description: "Keeps track of empty lines around class bodies." 31 | Enabled: false 32 | 33 | Layout/EmptyLinesAroundModuleBody: 34 | Description: "Keeps track of empty lines around module bodies." 35 | Enabled: false 36 | 37 | Layout/EmptyLinesAroundMethodBody: 38 | Description: "Keeps track of empty lines around method bodies." 39 | Enabled: false 40 | 41 | Layout/EndAlignment: 42 | Description: 'Align ends correctly.' 43 | Enabled: false 44 | 45 | Layout/InitialIndentation: 46 | Description: >- 47 | Checks the indentation of the first non-blank non-comment line in a file. 48 | Enabled: false 49 | 50 | Layout/FirstArgumentIndentation: 51 | Description: 'Checks the indentation of the first parameter in a method call.' 52 | Enabled: false 53 | 54 | 55 | Layout/IndentationConsistency: 56 | Description: 'Keep indentation straight.' 57 | Enabled: false 58 | 59 | 60 | Layout/FirstArrayElementIndentation: 61 | Description: >- 62 | Checks the indentation of the first element in an array 63 | literal. 64 | Enabled: false 65 | 66 | Layout/AssignmentIndentation: 67 | Description: >- 68 | Checks the indentation of the first line of the 69 | right-hand-side of a multi-line assignment. 70 | Enabled: false 71 | 72 | Layout/FirstHashElementIndentation: 73 | Description: 'Checks the indentation of the first key in a hash literal.' 74 | Enabled: false 75 | 76 | Layout/IndentationStyle: 77 | Description: 'No hard tabs.' 78 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 79 | Enabled: false 80 | 81 | Layout/MultilineAssignmentLayout: 82 | Description: 'Check for a newline after the assignment operator in multi-line assignments.' 83 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-conditional-assignment' 84 | Enabled: false 85 | 86 | Layout/MultilineArrayBraceLayout: 87 | Description: >- 88 | Checks that the closing brace in an array literal is 89 | either on the same line as the last array element, or 90 | a new line. 91 | Enabled: false 92 | 93 | Layout/MultilineBlockLayout: 94 | Description: 'Ensures newlines after multiline block do statements.' 95 | Enabled: false 96 | 97 | Layout/MultilineHashBraceLayout: 98 | Description: >- 99 | Checks that the closing brace in a hash literal is 100 | either on the same line as the last hash element, or 101 | a new line. 102 | Enabled: false 103 | 104 | Layout/MultilineMethodCallBraceLayout: 105 | Description: >- 106 | Checks that the closing brace in a method call is 107 | either on the same line as the last method argument, or 108 | a new line. 109 | Enabled: false 110 | 111 | Layout/MultilineMethodCallIndentation: 112 | Description: >- 113 | Checks indentation of method calls with the dot operator 114 | that span more than one line. 115 | Enabled: false 116 | 117 | Layout/MultilineMethodDefinitionBraceLayout: 118 | Description: >- 119 | Checks that the closing brace in a method definition is 120 | either on the same line as the last method parameter, or 121 | a new line. 122 | Enabled: false 123 | 124 | Layout/MultilineOperationIndentation: 125 | Description: >- 126 | Checks indentation of binary operations that span more than 127 | one line. 128 | Enabled: false 129 | 130 | 131 | Layout/RescueEnsureAlignment: 132 | Description: 'Align rescues and ensures correctly.' 133 | Enabled: false 134 | 135 | Layout/SpaceBeforeFirstArg: 136 | Description: >- 137 | Checks that exactly one space is used between a method name 138 | and the first argument for method calls without parentheses. 139 | Enabled: false 140 | 141 | Layout/SpaceAfterColon: 142 | Description: 'Use spaces after colons.' 143 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 144 | Enabled: false 145 | 146 | Layout/SpaceAfterComma: 147 | Description: 'Use spaces after commas.' 148 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 149 | Enabled: false 150 | 151 | Layout/SpaceAfterSemicolon: 152 | Description: 'Use spaces after semicolons.' 153 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 154 | Enabled: false 155 | 156 | Layout/SpaceBeforeComment: 157 | Description: >- 158 | Checks for missing space between code and a comment on the 159 | same line. 160 | Enabled: false 161 | 162 | Layout/SpaceBeforeSemicolon: 163 | Description: 'No spaces before semicolons.' 164 | Enabled: false 165 | 166 | Layout/SpaceInsideBlockBraces: 167 | Description: >- 168 | Checks that block braces have or don't have surrounding space. 169 | For blocks taking parameters, checks that the left brace has 170 | or doesn't have trailing space. 171 | Enabled: false 172 | 173 | Layout/SpaceAroundBlockParameters: 174 | Description: 'Checks the spacing inside and after block parameters pipes.' 175 | Enabled: false 176 | 177 | Layout/SpaceInsideArrayPercentLiteral: 178 | Description: 'No unnecessary additional spaces between elements in %i/%w literals.' 179 | Enabled: false 180 | 181 | Layout/SpaceInsidePercentLiteralDelimiters: 182 | Description: 'No unnecessary spaces inside delimiters of %i/%w/%x literals.' 183 | Enabled: false 184 | 185 | ################# Style ####################### 186 | 187 | Style/AsciiComments: 188 | Description: 'Use only ascii symbols in comments.' 189 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' 190 | Enabled: false 191 | 192 | Style/AutoResourceCleanup: 193 | Description: 'Suggests the usage of an auto resource cleanup version of a method (if available).' 194 | Enabled: false 195 | 196 | Style/CollectionMethods: 197 | Description: 'Preferred collection methods.' 198 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size' 199 | Enabled: false 200 | 201 | Style/CommentAnnotation: 202 | Description: >- 203 | Checks formatting of special comments 204 | (TODO, FIXME, OPTIMIZE, HACK, REVIEW). 205 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' 206 | Enabled: false 207 | 208 | Style/Copyright: 209 | Description: 'Include a copyright notice in each file before any code.' 210 | Enabled: false 211 | 212 | Style/Encoding: 213 | Description: 'Use UTF-8 as the source file encoding.' 214 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#utf-8' 215 | Enabled: false 216 | 217 | Style/IfUnlessModifier: 218 | Description: >- 219 | Favor modifier if/unless usage when you have a 220 | single-line body. 221 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' 222 | Enabled: false 223 | 224 | Style/ImplicitRuntimeError: 225 | Description: >- 226 | Use `raise` or `fail` with an explicit exception class and 227 | message, rather than just a message. 228 | Enabled: false 229 | 230 | Style/InlineComment: 231 | Description: 'Avoid inline comments.' 232 | Enabled: false 233 | 234 | Style/MethodCalledOnDoEndBlock: 235 | Description: 'Avoid chaining a method call on a do...end block.' 236 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 237 | Enabled: false 238 | 239 | Style/MissingElse: 240 | Description: >- 241 | Require if/case expressions to have an else branches. 242 | If enabled, it is recommended that 243 | Style/UnlessElse and Style/EmptyElse be enabled. 244 | This will conflict with Style/EmptyElse if 245 | Style/EmptyElse is configured to style "both" 246 | Enabled: false 247 | EnforcedStyle: both 248 | SupportedStyles: 249 | # if - warn when an if expression is missing an else branch 250 | # case - warn when a case expression is missing an else branch 251 | # both - warn when an if or case expression is missing an else branch 252 | - if 253 | - case 254 | - both 255 | 256 | Style/OptionHash: 257 | Description: "Don't use option hashes when you can use keyword arguments." 258 | Enabled: false 259 | 260 | Style/StringMethods: 261 | Description: 'Checks if configured preferred methods are used over non-preferred.' 262 | Enabled: false 263 | 264 | Style/ClassAndModuleChildren: 265 | Description: 'Checks style of children classes and modules.' 266 | Enabled: false 267 | 268 | Style/ClassCheck: 269 | Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' 270 | Enabled: false 271 | 272 | Style/ConditionalAssignment: 273 | Description: >- 274 | Use the return value of `if` and `case` statements for 275 | assignment to a variable and variable comparison instead 276 | of assigning that variable inside of each branch. 277 | Enabled: false 278 | 279 | Style/Documentation: 280 | Description: 'Document classes and non-namespace modules.' 281 | Enabled: false 282 | Exclude: 283 | - 'spec/**/*' 284 | - 'test/**/*' 285 | 286 | Style/DoubleNegation: 287 | Description: 'Checks for uses of double negation (!!).' 288 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' 289 | Enabled: false 290 | 291 | Style/EachForSimpleLoop: 292 | Description: >- 293 | Use `Integer#times` for a simple loop which iterates a fixed 294 | number of times. 295 | Enabled: false 296 | 297 | Style/EachWithObject: 298 | Description: 'Prefer `each_with_object` over `inject` or `reduce`.' 299 | Enabled: false 300 | 301 | Style/EmptyElse: 302 | Description: 'Avoid empty else-clauses.' 303 | Enabled: false 304 | 305 | Style/EmptyCaseCondition: 306 | Description: 'Avoid empty condition in case statements.' 307 | Enabled: false 308 | 309 | Style/EndBlock: 310 | Description: 'END blocks should not be placed inside method definitions.' 311 | Enabled: false 312 | 313 | Style/FormatString: 314 | Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' 315 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' 316 | Enabled: false 317 | 318 | Style/GuardClause: 319 | Description: 'Check for conditionals that can be replaced with guard clauses' 320 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 321 | Enabled: false 322 | 323 | Style/IfInsideElse: 324 | Description: 'Finds if nodes inside else, which can be converted to elsif.' 325 | Enabled: false 326 | 327 | Style/IdenticalConditionalBranches: 328 | Description: >- 329 | Checks that conditional statements do not have an identical 330 | line at the end of each branch, which can validly be moved 331 | out of the conditional. 332 | Enabled: false 333 | 334 | Style/LineEndConcatenation: 335 | Description: >- 336 | Use \ instead of + or << to concatenate two string literals at 337 | line end. 338 | Enabled: false 339 | 340 | Style/MethodDefParentheses: 341 | Description: >- 342 | Checks if the method definitions have or don't have 343 | parentheses. 344 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 345 | Enabled: false 346 | 347 | Style/MultilineBlockChain: 348 | Description: 'Avoid multi-line chains of blocks.' 349 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 350 | Enabled: false 351 | 352 | Style/MutableConstant: 353 | Description: 'Do not assign mutable objects to constants.' 354 | Enabled: false 355 | 356 | Style/NilComparison: 357 | Description: 'Prefer x.nil? to x == nil.' 358 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 359 | Enabled: false 360 | 361 | Style/NegatedIf: 362 | Description: >- 363 | Favor unless over if for negative conditions 364 | (or control flow or). 365 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' 366 | Enabled: false 367 | 368 | Style/NestedParenthesizedCalls: 369 | Description: >- 370 | Parenthesize method calls which are nested inside the 371 | argument list of another parenthesized method call. 372 | Enabled: false 373 | 374 | Style/NumericPredicate: 375 | Description: >- 376 | Checks for the use of predicate- or comparison methods for 377 | numeric comparisons. 378 | Enabled: false 379 | 380 | Style/OneLineConditional: 381 | Description: >- 382 | Favor the ternary operator(?:) over 383 | if/then/else/end constructs. 384 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' 385 | Enabled: false 386 | 387 | Style/PercentQLiterals: 388 | Description: 'Checks if uses of %Q/%q match the configured preference.' 389 | Enabled: false 390 | 391 | Style/PerlBackrefs: 392 | Description: 'Avoid Perl-style regex back references.' 393 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' 394 | Enabled: false 395 | 396 | Style/Proc: 397 | Description: 'Use proc instead of Proc.new.' 398 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' 399 | Enabled: false 400 | 401 | Style/RedundantFreeze: 402 | Description: "Checks usages of Object#freeze on immutable objects." 403 | Enabled: false 404 | 405 | Style/RegexpLiteral: 406 | Description: 'Use / or %r around regular expressions.' 407 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' 408 | Enabled: false 409 | 410 | Style/RedundantParentheses: 411 | Description: "Checks for parentheses that seem not to serve any purpose." 412 | Enabled: false 413 | 414 | Style/SingleLineBlockParams: 415 | Description: 'Enforces the names of some block params.' 416 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' 417 | Enabled: false 418 | 419 | Style/SingleLineMethods: 420 | Description: 'Avoid single-line methods.' 421 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' 422 | Enabled: false 423 | 424 | Style/StringLiterals: 425 | Description: 'Checks if uses of quotes match the configured preference.' 426 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' 427 | Enabled: false 428 | 429 | Style/StringLiteralsInInterpolation: 430 | Description: >- 431 | Checks if uses of quotes inside expressions in interpolated 432 | strings match the configured preference. 433 | Enabled: false 434 | 435 | Style/SymbolArray: 436 | Description: 'Use %i or %I for arrays of symbols.' 437 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-i' 438 | Enabled: false 439 | 440 | Style/SymbolLiteral: 441 | Description: 'Use plain symbols instead of string symbols when possible.' 442 | Enabled: false 443 | 444 | Style/SymbolProc: 445 | Description: 'Use symbols as procs instead of blocks when possible.' 446 | Enabled: false 447 | 448 | Style/TernaryParentheses: 449 | Description: 'Checks for use of parentheses around ternary conditions.' 450 | Enabled: false 451 | 452 | Style/TrailingCommaInArguments: 453 | Description: 'Checks for trailing comma in argument lists.' 454 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' 455 | Enabled: false 456 | 457 | Style/TrailingCommaInArrayLiteral: 458 | Description: 'Checks for trailing comma in array literals.' 459 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 460 | Enabled: false 461 | 462 | Style/TrailingCommaInHashLiteral: 463 | Description: 'Checks for trailing comma in hash literals.' 464 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 465 | Enabled: false 466 | 467 | Style/RedundantCapitalW: 468 | Description: 'Checks for %W when interpolation is not needed.' 469 | Enabled: false 470 | 471 | Style/RedundantInterpolation: 472 | Description: 'Checks for strings that are just an interpolated expression.' 473 | Enabled: false 474 | 475 | Style/TrailingUnderscoreVariable: 476 | Description: >- 477 | Checks for the usage of unneeded trailing underscores at the 478 | end of parallel variable assignment. 479 | AllowNamedUnderscoreVariables: true 480 | Enabled: false 481 | 482 | Style/WordArray: 483 | Description: 'Use %w or %W for arrays of words.' 484 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' 485 | Enabled: false 486 | 487 | Style/ZeroLengthPredicate: 488 | Description: 'Use #empty? when testing for objects of length 0.' 489 | Enabled: false 490 | 491 | Style/RedundantSortBy: 492 | Description: 'Use `sort` instead of `sort_by { |x| x }`.' 493 | Enabled: false 494 | 495 | Style/Sample: 496 | Description: >- 497 | Use `sample` instead of `shuffle.first`, 498 | `shuffle.last`, and `shuffle[Fixnum]`. 499 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' 500 | Enabled: false 501 | 502 | ################### Metrics ################################ 503 | 504 | Metrics/AbcSize: 505 | Description: >- 506 | A calculated magnitude based on number of assignments, 507 | branches, and conditions. 508 | Reference: 'http://c2.com/cgi/wiki?AbcMetric' 509 | Enabled: false 510 | 511 | Metrics/ClassLength: 512 | Description: 'Avoid classes longer than 100 lines of code.' 513 | Enabled: false 514 | 515 | Metrics/ModuleLength: 516 | Description: 'Avoid modules longer than 100 lines of code.' 517 | Enabled: false 518 | 519 | Metrics/CyclomaticComplexity: 520 | Description: >- 521 | A complexity metric that is strongly correlated to the number 522 | of test cases needed to validate a method. 523 | Enabled: false 524 | 525 | Metrics/PerceivedComplexity: 526 | Description: >- 527 | A complexity metric geared towards measuring complexity for a 528 | human reader. 529 | Enabled: false 530 | 531 | ################### Lint ################################ 532 | ## Warnings 533 | Lint/AmbiguousRegexpLiteral: 534 | Description: >- 535 | Checks for ambiguous regexp literals in the first argument of 536 | a method invocation without parentheses. 537 | Enabled: false 538 | 539 | Lint/AssignmentInCondition: 540 | Description: "Don't use assignment in conditions." 541 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' 542 | Enabled: false 543 | 544 | Lint/DeprecatedClassMethods: 545 | Description: 'Check for deprecated class method calls.' 546 | Enabled: false 547 | 548 | Lint/DuplicateMethods: 549 | Description: 'Check for duplicate method definitions.' 550 | Enabled: false 551 | 552 | Lint/EachWithObjectArgument: 553 | Description: 'Check for immutable argument given to each_with_object.' 554 | Enabled: false 555 | 556 | Lint/ElseLayout: 557 | Description: 'Check for odd code arrangement in an else block.' 558 | Enabled: false 559 | 560 | Lint/EmptyEnsure: 561 | Description: 'Checks for empty ensure block.' 562 | Enabled: false 563 | 564 | Lint/EmptyInterpolation: 565 | Description: 'Checks for empty string interpolation.' 566 | Enabled: false 567 | 568 | Lint/FlipFlop: 569 | Description: 'Checks for flip flops' 570 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' 571 | Enabled: false 572 | 573 | Lint/FloatOutOfRange: 574 | Description: >- 575 | Catches floating-point literals too large or small for Ruby to 576 | represent. 577 | Enabled: false 578 | 579 | Lint/FormatParameterMismatch: 580 | Description: 'The number of parameters to format/sprint must match the fields.' 581 | Enabled: false 582 | 583 | Lint/ImplicitStringConcatenation: 584 | Description: >- 585 | Checks for adjacent string literals on the same line, which 586 | could better be represented as a single string literal. 587 | Enabled: false 588 | 589 | Lint/IneffectiveAccessModifier: 590 | Description: >- 591 | Checks for attempts to use `private` or `protected` to set 592 | the visibility of a class method, which does not work. 593 | Enabled: false 594 | 595 | Lint/InheritException: 596 | Description: 'Avoid inheriting from the `Exception` class.' 597 | Enabled: false 598 | 599 | Lint/LiteralAsCondition: 600 | Description: 'Checks of literals used in conditions.' 601 | Enabled: false 602 | 603 | Lint/LiteralInInterpolation: 604 | Description: 'Checks for literals used in interpolation.' 605 | Enabled: false 606 | 607 | Lint/MissingSuper: 608 | Enabled: false 609 | 610 | Lint/NextWithoutAccumulator: 611 | Description: >- 612 | Do not omit the accumulator when calling `next` 613 | in a `reduce`/`inject` block. 614 | Enabled: false 615 | 616 | Lint/NonLocalExitFromIterator: 617 | Description: 'Do not use return in iterator to cause non-local exit.' 618 | Enabled: false 619 | 620 | Lint/ParenthesesAsGroupedExpression: 621 | Description: >- 622 | Checks for method calls with a space before the opening 623 | parenthesis. 624 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 625 | Enabled: false 626 | 627 | Lint/PercentStringArray: 628 | Description: >- 629 | Checks for unwanted commas and quotes in %w/%W literals. 630 | Enabled: false 631 | 632 | Lint/PercentSymbolArray: 633 | Description: >- 634 | Checks for unwanted commas and colons in %i/%I literals. 635 | Enabled: false 636 | 637 | Lint/RandOne: 638 | Description: >- 639 | Checks for `rand(1)` calls. Such calls always return `0` 640 | and most likely a mistake. 641 | Enabled: false 642 | 643 | Lint/RequireParentheses: 644 | Description: >- 645 | Use parentheses in the method call to avoid confusion 646 | about precedence. 647 | Enabled: false 648 | 649 | Lint/ShadowedException: 650 | Description: >- 651 | Avoid rescuing a higher level exception 652 | before a lower level exception. 653 | Enabled: false 654 | 655 | Lint/ShadowingOuterLocalVariable: 656 | Description: >- 657 | Do not use the same name as outer local variable 658 | for block arguments or block local variables. 659 | Enabled: false 660 | 661 | Lint/RedundantCopDisableDirective: 662 | Description: >- 663 | Checks for rubocop:disable comments that can be removed. 664 | Note: this cop is not disabled when disabling all cops. 665 | It must be explicitly disabled. 666 | Enabled: false 667 | 668 | Lint/UnderscorePrefixedVariableName: 669 | Description: 'Do not use prefix `_` for a variable that is used.' 670 | Enabled: false 671 | 672 | Lint/UselessAccessModifier: 673 | Description: 'Checks for useless access modifiers.' 674 | Enabled: false 675 | ContextCreatingMethods: [] 676 | 677 | Lint/UselessElseWithoutRescue: 678 | Description: 'Checks for useless `else` in `begin..end` without `rescue`.' 679 | Enabled: false 680 | 681 | Lint/UselessSetterCall: 682 | Description: 'Checks for useless setter call to a local variable.' 683 | Enabled: false 684 | 685 | Lint/Void: 686 | Description: 'Possible use of operator/literal/variable in void context.' 687 | Enabled: false 688 | 689 | #################### Rails ################################## 690 | 691 | Rails/ActionFilter: 692 | Description: 'Enforces consistent use of action filter methods.' 693 | Enabled: false 694 | 695 | Rails/Date: 696 | Description: >- 697 | Checks the correct usage of date aware methods, 698 | such as Date.today, Date.current etc. 699 | Enabled: false 700 | 701 | Rails/Delegate: 702 | Description: 'Prefer delegate method for delegations.' 703 | Enabled: false 704 | 705 | Rails/Exit: 706 | Description: >- 707 | Favor `fail`, `break`, `return`, etc. over `exit` in 708 | application or library code outside of Rake files to avoid 709 | exits during unit testing or running in production. 710 | Enabled: false 711 | 712 | Rails/FindBy: 713 | Description: 'Prefer find_by over where.first.' 714 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#find_by' 715 | Enabled: false 716 | 717 | Rails/FindEach: 718 | Description: 'Prefer all.find_each over all.find.' 719 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#find-each' 720 | Enabled: false 721 | 722 | Rails/HasAndBelongsToMany: 723 | Description: 'Prefer has_many :through to has_and_belongs_to_many.' 724 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#has-many-through' 725 | Enabled: false 726 | 727 | Rails/Output: 728 | Description: 'Checks for calls to puts, print, etc.' 729 | Enabled: false 730 | 731 | Rails/PluralizationGrammar: 732 | Description: 'Checks for incorrect grammar when using methods like `3.day.ago`.' 733 | Enabled: false 734 | 735 | Rails/ReadWriteAttribute: 736 | Description: >- 737 | Checks for read_attribute(:attr) and 738 | write_attribute(:attr, val). 739 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#read-attribute' 740 | Enabled: false 741 | 742 | Rails/RequestReferer: 743 | Description: 'Use consistent syntax for request.referer.' 744 | Enabled: false 745 | 746 | Rails/ScopeArgs: 747 | Description: 'Checks the arguments of ActiveRecord scopes.' 748 | Enabled: false 749 | 750 | Rails/TimeZone: 751 | Description: 'Checks the correct usage of time zone aware methods.' 752 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' 753 | Reference: 'http://danilenko.org/2012/7/6/rails_timezones' 754 | Enabled: false 755 | 756 | Rails/UniqBeforePluck: 757 | Description: 'Prefer the use of uniq or distinct before pluck.' 758 | Enabled: false 759 | 760 | Rails/Validation: 761 | Description: 'Use validates :attribute, hash of validations.' 762 | Enabled: false 763 | 764 | ################# Naming #################### 765 | 766 | Naming/VariableName: 767 | Description: 'Use the configured style when naming variables.' 768 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 769 | Enabled: false 770 | 771 | -------------------------------------------------------------------------------- /.rubocop-enforced.yml: -------------------------------------------------------------------------------- 1 | # Things added to this file are enforced by CI 2 | require: 3 | - rubocop-rails 4 | AllCops: 5 | DisabledByDefault: true 6 | Include: 7 | - '**/*.rb' 8 | - '**/*.rake' 9 | - '**/config.ru' 10 | - '**/Gemfile' 11 | - '**/Rakefile' 12 | 13 | ################# Layout ################# 14 | Layout/DefEndAlignment: 15 | Description: 'Align ends corresponding to defs correctly.' 16 | EnforcedStyleAlignWith: start_of_line 17 | Enabled: true 18 | 19 | Layout/EmptyLines: 20 | Description: "This cop checks for two or more consecutive blank lines." 21 | StyleGuide: "https://github.com/rubocop-hq/ruby-style-guide#two-or-more-empty-lines" 22 | Enabled: true 23 | 24 | Layout/EmptyLineBetweenDefs: 25 | Description: 'Use empty lines between defs.' 26 | # EmptyLineBetweenClassDefs disabled for now to preserve old behaviour 27 | # added in 1.4.0 https://github.com/rubocop/rubocop/releases/tag/v1.4.0 28 | EmptyLineBetweenClassDefs: false 29 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' 30 | Enabled: true 31 | 32 | Layout/EndOfLine: 33 | Description: 'Use Unix-style line endings.' 34 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' 35 | Enabled: true 36 | 37 | Layout/ExtraSpacing: 38 | AllowForAlignment: true 39 | Description: 'Do not use unnecessary spacing.' 40 | Enabled: true 41 | 42 | Layout/HeredocIndentation: 43 | Description: >- 44 | This cops checks the indentation of the here document bodies. 45 | The bodies are indented one step. In Ruby 2.3 or newer, squiggly heredocs (<<~) should be used. 46 | Enabled: true 47 | 48 | Layout/SpaceAfterMethodName: 49 | Description: >- 50 | Do not put a space between a method name and the opening 51 | parenthesis in a method definition. 52 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 53 | Enabled: true 54 | 55 | Layout/SpaceAroundKeyword: 56 | Description: 'Use a space around keywords if appropriate.' 57 | Enabled: true 58 | 59 | Layout/SpaceAroundOperators: 60 | Description: 'Use a single space around operators.' 61 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 62 | Enabled: true 63 | 64 | Layout/SpaceBeforeBlockBraces: 65 | Description: >- 66 | Checks that the left block brace has or doesn't have space 67 | before it. 68 | Enabled: true 69 | Exclude: 70 | - "**/spec/**/*.rb" 71 | 72 | Layout/SpaceBeforeComma: 73 | Description: 'No spaces before commas.' 74 | Enabled: true 75 | 76 | Layout/SpaceInsideHashLiteralBraces: 77 | Description: "Use spaces inside hash literal braces - or don't." 78 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 79 | Enabled: true 80 | 81 | Layout/TrailingEmptyLines: 82 | # See also: https://robots.thoughtbot.com/no-newline-at-end-of-file 83 | Description: 'Checks trailing blank lines and final newline.' 84 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' 85 | Enabled: true 86 | 87 | Layout/TrailingWhitespace: 88 | Enabled: true 89 | 90 | ################# Lint ####################### 91 | Lint/CircularArgumentReference: 92 | Description: "Default values in optional keyword arguments and optional ordinal arguments should not refer back to the name of the argument." 93 | Enabled: true 94 | 95 | Lint/Debugger: 96 | Description: 'Check for debugger calls.' 97 | Enabled: true 98 | 99 | Lint/DeprecatedClassMethods: 100 | Enabled: true 101 | 102 | Lint/DuplicateHashKey: 103 | Description: 'Check for duplicate keys in hash literals.' 104 | Enabled: true 105 | 106 | Lint/EnsureReturn: 107 | Enabled: true 108 | 109 | Lint/InheritException: 110 | Enabled: true 111 | 112 | Lint/NestedMethodDefinition: 113 | Description: 'Do not use nested method definitions.' 114 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' 115 | Enabled: true 116 | 117 | Lint/RescueException: 118 | Enabled: true 119 | 120 | Lint/RedundantStringCoercion: 121 | Enabled: true 122 | 123 | Lint/UnreachableCode: 124 | Enabled: true 125 | 126 | Lint/UnusedBlockArgument: 127 | Description: 'Checks for unused block arguments.' 128 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 129 | Enabled: true 130 | 131 | ################# Naming ####################### 132 | Naming/AsciiIdentifiers: 133 | Description: 'Use only ascii symbols in identifiers.' 134 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' 135 | Enabled: true 136 | 137 | Naming/ClassAndModuleCamelCase: 138 | Description: 'Use CamelCase for classes and modules.' 139 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' 140 | Enabled: true 141 | 142 | Naming/ConstantName: 143 | Description: 'Constants should use SCREAMING_SNAKE_CASE.' 144 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' 145 | Enabled: true 146 | 147 | Naming/FileName: 148 | Description: 'Use snake_case for source file names.' 149 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' 150 | Enabled: true 151 | 152 | ################# Style ####################### 153 | Style/ArrayJoin: 154 | Description: 'Use Array#join instead of Array#*.' 155 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' 156 | Enabled: true 157 | 158 | Style/BeginBlock: 159 | Description: 'Avoid the use of BEGIN blocks.' 160 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' 161 | Enabled: true 162 | 163 | Style/BlockComments: 164 | Description: 'Do not use block comments.' 165 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' 166 | Enabled: true 167 | 168 | Style/BlockDelimiters: 169 | Description: >- 170 | Avoid using {...} for multi-line blocks (multiline chaining is 171 | always ugly). 172 | Prefer {...} over do...end for single-line blocks. 173 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 174 | Enabled: true 175 | 176 | Style/EndBlock: 177 | Description: 'Avoid the use of END blocks.' 178 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' 179 | Enabled: true 180 | 181 | Style/HashSyntax: 182 | Enabled: true 183 | EnforcedStyle: ruby19_no_mixed_keys 184 | Exclude: 185 | 186 | Style/IfUnlessModifierOfIfUnless: 187 | Description: >- 188 | Avoid modifier if/unless usage on conditionals. 189 | Enabled: true 190 | 191 | Style/IfWithSemicolon: 192 | Description: 'Do not use if x; .... Use the ternary operator instead.' 193 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' 194 | Enabled: true 195 | 196 | Style/MethodCallWithoutArgsParentheses: 197 | Description: 'Do not use parentheses for method calls with no arguments.' 198 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' 199 | Enabled: true 200 | 201 | Style/MissingRespondToMissing: 202 | Enabled: true 203 | 204 | Style/RaiseArgs: 205 | Description: 'Checks the arguments passed to raise/fail.' 206 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' 207 | EnforcedStyle: compact 208 | Enabled: false 209 | 210 | Style/RedundantReturn: 211 | Description: "Don't use return where it's not required." 212 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' 213 | Enabled: true 214 | 215 | Style/Semicolon: 216 | Description: "Don't use semicolons to terminate expressions." 217 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' 218 | Enabled: true 219 | 220 | Style/SpecialGlobalVars: 221 | Description: 'Avoid Perl-style global variables.' 222 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' 223 | Enabled: true 224 | 225 | Style/StringLiterals: 226 | EnforcedStyle: double_quotes 227 | Description: 'Prefer double-quotes unless your string literal contains " or escape characters you want to suppress.' 228 | Enabled: true 229 | 230 | ################# Rails Cops ########################## 231 | 232 | Rails/DynamicFindBy: 233 | Description: "Favor the use of find_by over where.take and find_by_attribute when you need to retrieve a single record by one or more attributes and return nil when the record is not found" 234 | Enabled: true 235 | 236 | Rails/EnumHash: 237 | Description: "This cop looks for enums written with array syntax." 238 | Enabled: true 239 | 240 | Rails/HttpPositionalArguments: 241 | Enabled: true 242 | 243 | Rails/ActiveRecordAliases: 244 | Enabled: true 245 | 246 | ################# Security #################### 247 | 248 | Rails/OutputSafety: 249 | Description: 'The use of `html_safe` or `raw` may be a security risk.' 250 | Enabled: true 251 | Exclude: 252 | - "**/spec/**/*.rb" 253 | 254 | Security/Eval: 255 | Description: 'The use of eval represents a serious security risk.' 256 | Enabled: true 257 | 258 | Security/JSONLoad: 259 | Enabled: true 260 | Exclude: 261 | - "**/spec/**/*.rb" 262 | - "script/**/*.rb" 263 | 264 | Security/MarshalLoad: 265 | Enabled: true 266 | 267 | Security/Open: 268 | Enabled: true 269 | 270 | Security/YAMLLoad: 271 | Enabled: true 272 | Exclude: 273 | - "**/spec/**/*.rb" 274 | - "script/*" 275 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-rails 2 | inherit_from: 3 | - .rubocop-enforced.yml 4 | - .rubocop-disabled.yml 5 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.6 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.1.6-bullseye 2 | 3 | RUN apt-get update -qq && apt-get install -y nodejs 4 | 5 | ENV APP_HOME /Switchboard 6 | WORKDIR $APP_HOME 7 | COPY . . 8 | 9 | RUN gem install bundler:2.2.21 && bundle install 10 | 11 | COPY ./scripts/entrypoint.sh /usr/bin/ 12 | RUN chmod +x /usr/bin/entrypoint.sh 13 | COPY ./scripts/resolve_docker_host.rb /usr/bin/ 14 | RUN chmod +x /usr/bin/resolve_docker_host.rb 15 | ENTRYPOINT ["entrypoint.sh"] 16 | 17 | EXPOSE 3013 18 | 19 | CMD ["rails", "server", "-p", "3013", "-b", "0.0.0.0"] 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | 8 | # Bundle edge Rails instead: gem "rails", github: "rails/rails" 9 | gem "rails", "7.0.8.4" 10 | # Use Puma as the app server 11 | gem "puma" 12 | # Use SCSS for stylesheets 13 | gem "sass-rails", "~> 5.0" 14 | # Use Uglifier as compressor for JavaScript assets 15 | gem "uglifier", ">= 1.3.0" 16 | # Use CoffeeScript for .coffee assets and views 17 | gem "coffee-rails", "~> 4.2" 18 | # Used to enforce code style 19 | gem "rubocop-rails", "2.15.0" 20 | # Use jquery as the JavaScript library 21 | gem "jquery-rails" 22 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 23 | gem "turbolinks", "~> 5" 24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 25 | gem "jbuilder", "~> 2.5" 26 | 27 | gem "http", "~> 5" 28 | gem "jwt", "2.5.0" 29 | gem "secure_headers", "6.3.1" 30 | 31 | group :development, :test do 32 | # Call "byebug" anywhere in the code to stop execution and get a debugger console 33 | gem "byebug", platform: :mri 34 | gem "dotenv-rails" 35 | end 36 | 37 | group :development do 38 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 39 | gem "web-console", ">= 3.3.0" 40 | gem "listen" 41 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 42 | gem "spring" 43 | gem "spring-watcher-listen" 44 | gem "pry" 45 | gem "pry-byebug" 46 | end 47 | 48 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 49 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 50 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (7.0.8.4) 5 | actionpack (= 7.0.8.4) 6 | activesupport (= 7.0.8.4) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | actionmailbox (7.0.8.4) 10 | actionpack (= 7.0.8.4) 11 | activejob (= 7.0.8.4) 12 | activerecord (= 7.0.8.4) 13 | activestorage (= 7.0.8.4) 14 | activesupport (= 7.0.8.4) 15 | mail (>= 2.7.1) 16 | net-imap 17 | net-pop 18 | net-smtp 19 | actionmailer (7.0.8.4) 20 | actionpack (= 7.0.8.4) 21 | actionview (= 7.0.8.4) 22 | activejob (= 7.0.8.4) 23 | activesupport (= 7.0.8.4) 24 | mail (~> 2.5, >= 2.5.4) 25 | net-imap 26 | net-pop 27 | net-smtp 28 | rails-dom-testing (~> 2.0) 29 | actionpack (7.0.8.4) 30 | actionview (= 7.0.8.4) 31 | activesupport (= 7.0.8.4) 32 | rack (~> 2.0, >= 2.2.4) 33 | rack-test (>= 0.6.3) 34 | rails-dom-testing (~> 2.0) 35 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 36 | actiontext (7.0.8.4) 37 | actionpack (= 7.0.8.4) 38 | activerecord (= 7.0.8.4) 39 | activestorage (= 7.0.8.4) 40 | activesupport (= 7.0.8.4) 41 | globalid (>= 0.6.0) 42 | nokogiri (>= 1.8.5) 43 | actionview (7.0.8.4) 44 | activesupport (= 7.0.8.4) 45 | builder (~> 3.1) 46 | erubi (~> 1.4) 47 | rails-dom-testing (~> 2.0) 48 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 49 | activejob (7.0.8.4) 50 | activesupport (= 7.0.8.4) 51 | globalid (>= 0.3.6) 52 | activemodel (7.0.8.4) 53 | activesupport (= 7.0.8.4) 54 | activerecord (7.0.8.4) 55 | activemodel (= 7.0.8.4) 56 | activesupport (= 7.0.8.4) 57 | activestorage (7.0.8.4) 58 | actionpack (= 7.0.8.4) 59 | activejob (= 7.0.8.4) 60 | activerecord (= 7.0.8.4) 61 | activesupport (= 7.0.8.4) 62 | marcel (~> 1.0) 63 | mini_mime (>= 1.1.0) 64 | activesupport (7.0.8.4) 65 | concurrent-ruby (~> 1.0, >= 1.0.2) 66 | i18n (>= 1.6, < 2) 67 | minitest (>= 5.1) 68 | tzinfo (~> 2.0) 69 | addressable (2.8.6) 70 | public_suffix (>= 2.0.2, < 6.0) 71 | ast (2.4.2) 72 | base64 (0.2.0) 73 | bindex (0.8.1) 74 | builder (3.3.0) 75 | byebug (11.1.3) 76 | coderay (1.1.3) 77 | coffee-rails (4.2.2) 78 | coffee-script (>= 2.2.0) 79 | railties (>= 4.0.0) 80 | coffee-script (2.4.1) 81 | coffee-script-source 82 | execjs 83 | coffee-script-source (1.12.2) 84 | concurrent-ruby (1.3.3) 85 | crass (1.0.6) 86 | date (3.3.4) 87 | domain_name (0.6.20240107) 88 | dotenv (2.8.1) 89 | dotenv-rails (2.8.1) 90 | dotenv (= 2.8.1) 91 | railties (>= 3.2) 92 | erubi (1.13.0) 93 | execjs (2.9.1) 94 | ffi (1.17.0) 95 | ffi-compiler (1.3.2) 96 | ffi (>= 1.15.5) 97 | rake 98 | globalid (1.2.1) 99 | activesupport (>= 6.1) 100 | http (5.2.0) 101 | addressable (~> 2.8) 102 | base64 (~> 0.1) 103 | http-cookie (~> 1.0) 104 | http-form_data (~> 2.2) 105 | llhttp-ffi (~> 0.5.0) 106 | http-cookie (1.0.6) 107 | domain_name (~> 0.5) 108 | http-form_data (2.3.0) 109 | i18n (1.14.5) 110 | concurrent-ruby (~> 1.0) 111 | jbuilder (2.12.0) 112 | actionview (>= 5.0.0) 113 | activesupport (>= 5.0.0) 114 | jquery-rails (4.6.0) 115 | rails-dom-testing (>= 1, < 3) 116 | railties (>= 4.2.0) 117 | thor (>= 0.14, < 2.0) 118 | json (2.7.2) 119 | jwt (2.5.0) 120 | listen (3.9.0) 121 | rb-fsevent (~> 0.10, >= 0.10.3) 122 | rb-inotify (~> 0.9, >= 0.9.10) 123 | llhttp-ffi (0.5.0) 124 | ffi-compiler (~> 1.0) 125 | rake (~> 13.0) 126 | loofah (2.22.0) 127 | crass (~> 1.0.2) 128 | nokogiri (>= 1.12.0) 129 | mail (2.8.1) 130 | mini_mime (>= 0.1.1) 131 | net-imap 132 | net-pop 133 | net-smtp 134 | marcel (1.0.4) 135 | method_source (1.1.0) 136 | mini_mime (1.1.5) 137 | mini_portile2 (2.8.7) 138 | minitest (5.23.1) 139 | net-imap (0.4.14) 140 | date 141 | net-protocol 142 | net-pop (0.1.2) 143 | net-protocol 144 | net-protocol (0.2.2) 145 | timeout 146 | net-smtp (0.5.0) 147 | net-protocol 148 | nio4r (2.7.3) 149 | nokogiri (1.13.10) 150 | mini_portile2 (~> 2.8.0) 151 | racc (~> 1.4) 152 | parallel (1.24.0) 153 | parser (3.3.3.0) 154 | ast (~> 2.4.1) 155 | racc 156 | pry (0.14.2) 157 | coderay (~> 1.1) 158 | method_source (~> 1.0) 159 | pry-byebug (3.8.0) 160 | byebug (~> 11.0) 161 | pry (~> 0.10) 162 | public_suffix (5.1.1) 163 | puma (6.4.2) 164 | nio4r (~> 2.0) 165 | racc (1.8.0) 166 | rack (2.2.9) 167 | rack-test (2.1.0) 168 | rack (>= 1.3) 169 | rails (7.0.8.4) 170 | actioncable (= 7.0.8.4) 171 | actionmailbox (= 7.0.8.4) 172 | actionmailer (= 7.0.8.4) 173 | actionpack (= 7.0.8.4) 174 | actiontext (= 7.0.8.4) 175 | actionview (= 7.0.8.4) 176 | activejob (= 7.0.8.4) 177 | activemodel (= 7.0.8.4) 178 | activerecord (= 7.0.8.4) 179 | activestorage (= 7.0.8.4) 180 | activesupport (= 7.0.8.4) 181 | bundler (>= 1.15.0) 182 | railties (= 7.0.8.4) 183 | rails-dom-testing (2.2.0) 184 | activesupport (>= 5.0.0) 185 | minitest 186 | nokogiri (>= 1.6) 187 | rails-html-sanitizer (1.5.0) 188 | loofah (~> 2.19, >= 2.19.1) 189 | railties (7.0.8.4) 190 | actionpack (= 7.0.8.4) 191 | activesupport (= 7.0.8.4) 192 | method_source 193 | rake (>= 12.2) 194 | thor (~> 1.0) 195 | zeitwerk (~> 2.5) 196 | rainbow (3.1.1) 197 | rake (13.2.1) 198 | rb-fsevent (0.11.2) 199 | rb-inotify (0.11.1) 200 | ffi (~> 1.0) 201 | regexp_parser (2.9.2) 202 | rexml (3.3.0) 203 | strscan 204 | rubocop (1.50.2) 205 | json (~> 2.3) 206 | parallel (~> 1.10) 207 | parser (>= 3.2.0.0) 208 | rainbow (>= 2.2.2, < 4.0) 209 | regexp_parser (>= 1.8, < 3.0) 210 | rexml (>= 3.2.5, < 4.0) 211 | rubocop-ast (>= 1.28.0, < 2.0) 212 | ruby-progressbar (~> 1.7) 213 | unicode-display_width (>= 2.4.0, < 3.0) 214 | rubocop-ast (1.30.0) 215 | parser (>= 3.2.1.0) 216 | rubocop-rails (2.15.0) 217 | activesupport (>= 4.2.0) 218 | rack (>= 1.1) 219 | rubocop (>= 1.7.0, < 2.0) 220 | ruby-progressbar (1.13.0) 221 | sass (3.7.4) 222 | sass-listen (~> 4.0.0) 223 | sass-listen (4.0.0) 224 | rb-fsevent (~> 0.9, >= 0.9.4) 225 | rb-inotify (~> 0.9, >= 0.9.7) 226 | sass-rails (5.1.0) 227 | railties (>= 5.2.0) 228 | sass (~> 3.1) 229 | sprockets (>= 2.8, < 4.0) 230 | sprockets-rails (>= 2.0, < 4.0) 231 | tilt (>= 1.1, < 3) 232 | secure_headers (6.3.1) 233 | spring (4.2.1) 234 | spring-watcher-listen (2.1.0) 235 | listen (>= 2.7, < 4.0) 236 | spring (>= 4) 237 | sprockets (3.7.3) 238 | base64 239 | concurrent-ruby (~> 1.0) 240 | rack (> 1, < 3) 241 | sprockets-rails (3.4.2) 242 | actionpack (>= 5.2) 243 | activesupport (>= 5.2) 244 | sprockets (>= 3.0.0) 245 | strscan (3.1.0) 246 | thor (1.3.1) 247 | tilt (2.3.0) 248 | timeout (0.4.1) 249 | turbolinks (5.2.1) 250 | turbolinks-source (~> 5.2) 251 | turbolinks-source (5.2.0) 252 | tzinfo (2.0.6) 253 | concurrent-ruby (~> 1.0) 254 | uglifier (4.2.0) 255 | execjs (>= 0.3.0, < 3) 256 | unicode-display_width (2.5.0) 257 | web-console (4.2.1) 258 | actionview (>= 6.0.0) 259 | activemodel (>= 6.0.0) 260 | bindex (>= 0.4.0) 261 | railties (>= 6.0.0) 262 | websocket-driver (0.7.6) 263 | websocket-extensions (>= 0.1.0) 264 | websocket-extensions (0.1.5) 265 | zeitwerk (2.6.15) 266 | 267 | PLATFORMS 268 | ruby 269 | 270 | DEPENDENCIES 271 | byebug 272 | coffee-rails (~> 4.2) 273 | dotenv-rails 274 | http (~> 5) 275 | jbuilder (~> 2.5) 276 | jquery-rails 277 | jwt (= 2.5.0) 278 | listen 279 | pry 280 | pry-byebug 281 | puma 282 | rails (= 7.0.8.4) 283 | rubocop-rails (= 2.15.0) 284 | sass-rails (~> 5.0) 285 | secure_headers (= 6.3.1) 286 | spring 287 | spring-watcher-listen 288 | turbolinks (~> 5) 289 | tzinfo-data 290 | uglifier (>= 1.3.0) 291 | web-console (>= 3.3.0) 292 | 293 | BUNDLED WITH 294 | 2.4.20 295 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Themis Solutions Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Third-Party Application 2 | 3 | **By using this sample code, you acknowledge that you have read, accepted and agree to be bound by and comply with the terms and conditions set out in the [Clio Developer Terms of Service](https://www.clio.com/partnerships/developers/developer-terms-service/).** 4 | 5 | This is an example third-party application that demonstrates the ability to use single sign-on (SSO) with Clio's authentication services. 6 | 7 | SSO makes it easy for Clio users to securely sign in and access your application at the click of a button using their Clio credentials, as opposed to having them create entirely new accounts and credentials for each third-party application they wish to utilize. 8 | 9 | Also included in this example application is making use of the access token obtained during SSO to make a successful API request. 10 | 11 | Note that this example code isn't in an ideal, refactored state. We've taken a "show your work" approach, clearly identifying the steps taken. In line with this we've also opted not to use some Ruby gems that might hide implementation details (such as `oauth2` that can automate part of the SSO flow for you). 12 | 13 | ## Prerequisites 14 | 15 | Whether you're a private developer or a company looking to integrate with Clio, the first step is to complete our [registration form](https://www.clio.com/partnerships/developers/get-started/). If you're looking to offer your integration to Clio customers, keep an eye out for an automatic follow-up email with instructions to setup your Developer Account on Clio Manage and Clio Identity. 16 | 17 | If you're already a Clio customer and are just looking to use our API for private use, you can get started right away. 18 | 19 | ### Clio Manage API Keys 20 | 21 | Clio Manage is the cloud-based legal practice management software that makes running a firm, organizing cases, and collaborating with clients from one place possible. 22 | 23 | Once your Developer Account has been set up, navigate to your [Developer Portal](http://app.clio.com/settings/developer_applications) within Clio Manage and follow our [instructions for creating a Clio application](https://app.clio.com/api/v4/documentation#section/Authorization-with-OAuth-2.0/Create-a-Clio-Application) on our Clio Manage API Documentation. Creating a Clio application will generate an application key and secret, which will be used to authorize your application with Clio and communicate with our Clio Manage API. 24 | 25 | ### Clio Identity API Keys 26 | 27 | Clio Identity is Clio's authentication service and identity provider. Similar to how you may see a "Login with GitHub" or "Login with Google" button when authenticating with an application on the web, third-party developers can leverage Clio Identity as an identity provider, cutting the time needed to write and maintain an authentication system for an application. 28 | 29 | To request a Clio Identity API key in order to implement SSO see our guide for [integrating with Clio Identity](https://developers.support.clio.com/hc/en-us/articles/4405288237723-Integrating-with-Clio-Identity-Single-Sign-on-with-Clio-). 30 | 31 | ### Setup 32 | 33 | First, clone the example application repository to your local environment: `git clone https://github.com/clio/example-third-party-application.git`. 34 | 35 | Then update the environment variables in `.env`: 36 | 1. Update the `CLIO_MANAGE_CLIENT_ID` and `CLIO_MANAGE_CLIENT_SECRET` values with your Clio Manage API Key and Secret 37 | 2. Update the `CLIO_MANAGE_SITE_URL` value with the region specific URL of your Clio Manage account: 38 | * `https://app.clio.com/` for the United States 39 | * `https://ca.app.clio.com/` for Canada 40 | * `https://eu.app.clio.com/` for Europe 41 | 3. Update the `CLIO_IDENTITY_CLIENT_ID` and `CLIO_IDENTITY_CLIENT_SECRET` values with your Clio Identity API Key and Secret 42 | 43 | You can then choose between a manual setup (install Ruby and Rails locally) or a docker setup (install Docker only). Manual setup instructions can be found in [docs/manual_setup.md](/docs/manual_setup.md) and the Docker setup instructions can be found in [docs/docker_setup.md](/docs/docker_setup.md). 44 | 45 | ## Example Entry Point 46 | 47 | ### SSO 48 | 49 | Both authentication with Clio Identity and authorization with Clio Manage start their flows in the `ApplicationController` (`app/controllers/application_controller.rb`). This file takes you through the steps outlined in our [Integrating with Clio Identity](https://developers.support.clio.com/hc/en-us/articles/4405288237723-Integrating-with-Clio-Identity-Single-Sign-on-with-Clio-) documentation (for performing authentication) and our [API Documentation](https://app.clio.com/api/v4/documentation#section/Authorization-with-OAuth-2.0/Obtaining-Authorization) (for obtaining authorization). 50 | 51 | ### API Requests 52 | 53 | The `ProfileController` (`app/controllers/profile_controller.rb`) uses the Clio Manage access token to make a simple API request to the `api/v4/users/who_am_i` endpoint. 54 | 55 | Meanwhile the `MatterController` (`app/controllers/matter_controller.rb`) uses the Clio Manage access token to make an API request to the `api/v4/matters` endpoint using [Unlimited Cursor Pagination](https://app.clio.com/api/v4/documentation#section/Paging/Unlimited-Cursor-Pagination). 56 | 57 | ## Support 58 | 59 | For technical inquiries, visit our [Clio Developers Help Center](https://developers.support.clio.com/hc/en-us), send us an email at api@clio.com, or visit our community-driven Slack workspace at [clio-public.slack.com](https://join.slack.com/t/clio-public/shared_invite/zt-qgkswsia-fyTprPgHGBR0TLAk7PENJw). 60 | 61 | For business and partnership inquiries, visit [https://www.clio.com/partnerships/developers/](https://www.clio.com/partnerships/developers/). 62 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require_relative "config/application" 2 | 3 | Rails.application.load_tasks 4 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | 7 | (function () { 8 | this.App || (this.App = {}); 9 | App.cable = ActionCable.createConsumer(); 10 | }.call(this)); 11 | -------------------------------------------------------------------------------- /app/assets/javascripts/welcome.js: -------------------------------------------------------------------------------- 1 | function receiveMessageFromPopup(event) { 2 | if (!(event.origin === "http://localhost:3013" || event.origin === "https://example-third-party-application.clio.dev") || !event.isTrusted) { 3 | console.log("You are not worthy, received message from unknown source!"); 4 | } else { 5 | if (event.data === "authentication_successful") { 6 | console.log( 7 | "Sign in was successful, redirect to the matter page.", 8 | event.data 9 | ); 10 | window.location.href = "/matter"; 11 | } else { 12 | console.log("Sign in failed.", event.data); 13 | } 14 | } 15 | } 16 | 17 | //Listen for message events 18 | window.addEventListener("message", receiveMessageFromPopup, false); 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | 17 | body { 18 | font-size: 18px; 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 20 | margin: 0 auto; 21 | background: #F2F3F5; 22 | } 23 | 24 | .container { 25 | margin: 0 auto; 26 | padding: 25px; 27 | margin-bottom: 20px; 28 | max-width: 768px; 29 | } 30 | 31 | .card { 32 | background: #fff; 33 | border-radius: 5px; 34 | border: 1px solid #D7DEE2; 35 | -webkit-box-shadow: 0px 0px 12px -2px rgba(0,0,0,0.25); 36 | -moz-box-shadow: 0px 0px 12px -2px rgba(0,0,0,0.25); 37 | box-shadow: 0px 0px 12px -2px rgba(0,0,0,0.25); 38 | 39 | &.card-login { 40 | max-width: 438px; 41 | margin: 0 auto; 42 | } 43 | 44 | .card-header { 45 | font-weight: 700; 46 | padding: 15px 25px; 47 | border-bottom: 1px solid #D7DEE2; 48 | display: flex; 49 | justify-content: space-between; 50 | align-items: center; 51 | } 52 | 53 | .card-body { 54 | padding: 35px 25px; 55 | 56 | h1, h4 { 57 | margin-top: 0px; 58 | } 59 | 60 | div { 61 | margin-top: 30px; 62 | } 63 | 64 | .data-table { 65 | border-collapse: collapse; 66 | width: 100%; 67 | 68 | tr { 69 | height: 60px; 70 | } 71 | 72 | tr:nth-child(even) { 73 | background-color: #d8f6fc; 74 | } 75 | } 76 | } 77 | 78 | .card-footer { 79 | border-top: 1px solid #D7DEE2; 80 | padding: 15px 25px; 81 | 82 | p { 83 | color: #576C82; 84 | font-weight: 200; 85 | line-height: 1.4; 86 | font-size: 16px; 87 | margin: 8px 0; 88 | } 89 | } 90 | } 91 | 92 | form { 93 | .input-group { 94 | display: flex; 95 | flex-direction: column; 96 | margin-bottom: 22px; 97 | } 98 | } 99 | 100 | label { 101 | font-size: 13px; 102 | font-weight: 300; 103 | } 104 | 105 | input { 106 | border-radius: 3px; 107 | padding: 2px 12px; 108 | margin-top: 5px; 109 | border: 1px solid #D7DEE2; 110 | height: 32px; 111 | transition: border 0.1s ease-in-out; 112 | 113 | &:focus { 114 | border: 1px solid #0075bb; 115 | outline: none; 116 | box-shadow: none; 117 | transition: border 0.1s ease-in-out; 118 | } 119 | } 120 | 121 | p { 122 | color: #263238; 123 | font-weight: 400; 124 | line-height: 1.4; 125 | font-size: 16px; 126 | margin: 8px 0; 127 | } 128 | 129 | pre { 130 | background-color: #f5f7f8; 131 | padding: 16px; 132 | border-radius: 5px; 133 | overflow: auto; 134 | } 135 | 136 | .divider { 137 | border-right: 1px solid black; 138 | height: 110px; 139 | margin: 0 20px; 140 | } 141 | 142 | .text-center { 143 | text-align: center; 144 | } 145 | -------------------------------------------------------------------------------- /app/assets/stylesheets/buttons.scss: -------------------------------------------------------------------------------- 1 | // SSO Button CSS from the Styleguide 2 | // https://developers.support.clio.com/hc/en-us/articles/4405337190683--Sign-in-with-Clio-Button-guidelines-for-applications 3 | :root { 4 | // Variables overridden for Switchboard theming 5 | --text: #ffffff; 6 | --font: "Helvetica Neue", Helvetica, Arial, sans-serif; 7 | --default-state-background: #10b9df; 8 | --default-state-border: #0c91af; 9 | --hover-state-background: var(--default-state-border); 10 | --hover-state-underline: #ffffff; 11 | --focus-state-shadow: #004670; 12 | --focus-state-border: #ffffff; 13 | } 14 | 15 | button, a.button { 16 | height: 32px; 17 | padding: 8px 12px; 18 | border-radius: 4px; 19 | border: 1px solid var(--default-state-border); 20 | background-color: var(--default-state-background); 21 | background-image: linear-gradient(-180deg, var(--default-state-background) 0%, var(--default-state-border) 100%); 22 | font-family: var(--font); 23 | color: var(--text); 24 | font-size: 13px; 25 | font-style: normal; 26 | font-weight: bold; 27 | align-items: center; 28 | display: flex; 29 | position: relative; 30 | box-sizing: border-box; 31 | 32 | &:hover, &:focus { 33 | background-image: linear-gradient(-180deg, var(--hover-state-background) 0%, var(--hover-state-background) 100%); 34 | } 35 | 36 | &:hover:before { 37 | content: ""; 38 | border-radius: 0 0 1px 1px; 39 | border-bottom: 2px solid var(--hover-state-underline); 40 | position: absolute; 41 | bottom: 1px; 42 | left: 1px; 43 | right: 1px; 44 | } 45 | 46 | &:focus:after { 47 | box-shadow: 0 0 0 2px var(--focus-state-shadow); 48 | content: ""; 49 | border-radius: 4px; 50 | border: 1px solid var(--focus-state-border); 51 | position: absolute; 52 | top: -2px; 53 | bottom: -2px; 54 | left: -2px; 55 | right: -2px; 56 | } 57 | 58 | img { 59 | padding-right: 8px; 60 | height: 16px; 61 | width: 16px; 62 | } 63 | 64 | // Custom styles 65 | width: -moz-available; 66 | width: -webkit-fill-available; 67 | justify-content: center; 68 | } 69 | 70 | a.button { 71 | display: -webkit-box; 72 | display: -ms-flexbox; 73 | display: inline-flex; 74 | -webkit-box-align: center; 75 | -ms-flex-align: center; 76 | -ms-flex-pack: distribute; 77 | justify-content: center; 78 | -webkit-box-flex: 0; 79 | -ms-flex: 0 0 auto; 80 | -webkit-box-sizing: border-box; 81 | margin: 0; 82 | cursor: pointer; 83 | text-decoration: none; 84 | transition: all 0.1s ease-in-out; 85 | } 86 | 87 | // Spacing for the pagination buttons 88 | .pagination { 89 | display: flex; 90 | justify-content: space-between; 91 | width: 100%; 92 | 93 | div { 94 | margin-top: 0px !important; 95 | } 96 | 97 | .button { 98 | &.previous { 99 | width: 200px; 100 | } 101 | 102 | &.next { 103 | width: 200px; 104 | } 105 | } 106 | } 107 | 108 | .right { 109 | text-align: right; 110 | 111 | .button { 112 | width: 200px; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/assets/stylesheets/nav_menu.scss: -------------------------------------------------------------------------------- 1 | .menu-icon { 2 | position: relative; 3 | display: inline-block; 4 | width: 1.25em; 5 | height: 0.8em; 6 | margin-right: 0.3em; 7 | border-top: 0.2em solid #fff; 8 | border-bottom: 0.2em solid #fff; 9 | } 10 | 11 | .menu-icon:before { 12 | content: ""; 13 | position: absolute; 14 | top: 0.3em; 15 | left: 0px; 16 | width: 100%; 17 | border-top: 0.2em solid #fff; 18 | } 19 | 20 | .nav-dropdown { 21 | float: left; 22 | overflow: hidden; 23 | 24 | .nav-dropdown-content { 25 | display: none; 26 | position: absolute; 27 | background-color: #10b9df; 28 | background-image: linear-gradient(-180deg, #0c91af 0%, #10b9df 100%); 29 | border-radius: 3px; 30 | z-index: 1; 31 | 32 | a { 33 | color: #FFFFFF; 34 | padding: 12px 16px; 35 | text-decoration: none; 36 | display: block; 37 | } 38 | 39 | a:hover { 40 | background-color: #0c91af; 41 | background-image: linear-gradient(-180deg, #0c91af 0%, #0c91af 100%); 42 | } 43 | 44 | a:hover, a:active { 45 | text-decoration: underline #FFFFFF solid 2px; 46 | } 47 | 48 | a:active { 49 | border: 2px solid #004670; 50 | } 51 | } 52 | } 53 | 54 | .nav-dropdown:hover .nav-dropdown-content { 55 | display: block; 56 | } -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | require "http" 2 | 3 | class ApplicationController < ActionController::Base 4 | protect_from_forgery with: :exception 5 | 6 | include IdentityAuthenticationHelper 7 | include ManageAuthorizationHelper 8 | 9 | # This method is performing the steps under "Obtaining Authorization" in the Integrating with Clio Identity documentation: 10 | # https://developers.support.clio.com/hc/en-us/articles/4405288237723-Integrating-with-Clio-Identity-Single-Sign-on-with-Clio- 11 | # 12 | # This will send the user to the Clio Identity Account page where they will log in and be given the option to grant 13 | # your application permission to access their Clio Identity information. After they have allowed access they will be 14 | # redirected to the specified redirect URI in this request. In this example that is `identity_callback` seen below. 15 | def authenticate_with_identity 16 | identity_state = SecureRandom.hex 17 | cookies.encrypted[:identity_state] = { 18 | value: identity_state, 19 | same_site: "None", 20 | secure: true, 21 | } 22 | 23 | redirect_to identity_authenticate_url(identity_state), allow_other_host: true 24 | end 25 | 26 | # This method is performing the steps under "Grant Approved" in the Integrating with Clio Identity documentation: 27 | # https://developers.support.clio.com/hc/en-us/articles/4405288237723-Integrating-with-Clio-Identity-Single-Sign-on-with-Clio- 28 | def identity_callback 29 | # First we ensure that the previous step was successful: the state is the same as what we sent in the previous 30 | # request and that we did successfully obtain a code. 31 | if !identity_state_valid?(params[:state]) 32 | render_identity_error("bad state", params.to_unsafe_h) 33 | return 34 | end 35 | if !params.has_key?(:code) 36 | render_identity_error("missing code", params.to_unsafe_h) 37 | return 38 | end 39 | 40 | # Then we make a request for the Clio Identity related information from the users account. 41 | response = HTTP.post(identity_token_url, form: identity_token_body(params[:code])) 42 | parsed_response = JSON.parse(response.body) 43 | 44 | # Make sure to handle any error cases, such as if authentication fails or the user revokes 45 | # your applications access grant. 46 | if response.code != 200 47 | render_identity_error("error getting id token", parsed_response) 48 | return 49 | end 50 | 51 | # The response has to be decoded (using the RS256 algorithm) and then validated (following 52 | # the Open ID specs). 53 | decoded_token = decode_and_validate_identity_token(parsed_response["id_token"]) 54 | 55 | # If validation passes, save the `id_token` for later use and move on to requesting 56 | # authorization in Manage. 57 | cookies.encrypted[:id_token] = { 58 | value: decoded_token, 59 | same_site: "None", 60 | secure: true, 61 | } 62 | authorize_with_manage 63 | rescue JwtDecodeError => e 64 | render_identity_error("JWT decode error", e.message) 65 | rescue JsonWebKeyError => e 66 | render_identity_error("error getting JSON web keys", JSON.parse(e.message)) 67 | rescue InvalidTokenError => e 68 | render_identity_error("invalid token", e.message) 69 | end 70 | 71 | # This method is performing the steps in step 1 of "Grant Type: Authorization Code" under "Obtaining Authorization" 72 | # in the Clio API Documentation: https://app.clio.com/api/v4/documentation#section/Authorization-with-OAuth-2.0/Obtaining-Authorization 73 | # 74 | # This will send the user to the Clio Manage Authorization page where they will log in and be given the option to 75 | # grant your application permission to access their Clio Manage information. After they have allowed access they will 76 | # be redirected to the specified redirect URI in this request. In this example that is `manage_callback` seen below. 77 | def authorize_with_manage 78 | manage_state = SecureRandom.hex 79 | cookies.encrypted[:manage_state] = { 80 | value: manage_state, 81 | same_site: "None", 82 | secure: true, 83 | } 84 | redirect_to manage_authorize_url(manage_state), allow_other_host: true 85 | end 86 | 87 | # This method is performing the steps in step 2 of "Grant Type: Authorization Code" under "Obtaining Authorization" 88 | # in the Clio API Documentation: https://app.clio.com/api/v4/documentation#section/Authorization-with-OAuth-2.0/Obtaining-Authorization 89 | def manage_callback 90 | # If `redirect_on_decline` was set to `true` in the first request we can verify that permission was granted in 91 | # the first step of Manage authorization. Here we also ensure that the state is the same as what we sent and 92 | # that we did successfully obtain a code. 93 | if params.has_key?(:error) 94 | render_manage_error(params[:error].sub("_", " "), params.to_unsafe_h) 95 | return 96 | end 97 | if !manage_state_valid?(params[:state]) 98 | render_manage_error("bad state", params.to_unsafe_h) 99 | return 100 | end 101 | if !params.has_key?(:code) 102 | render_manage_error("missing code", params.to_unsafe_h) 103 | return 104 | end 105 | 106 | # Then we make a request for the Clio Manaage access token. 107 | response = HTTP.headers("Content-Type" => "application/x-www-form-urlencoded").post(manage_token_url, form: manage_token_body(params[:code])) 108 | parsed_response = JSON.parse(response.body) 109 | 110 | # Make sure to handle any error cases. 111 | if response.code != 200 112 | render_manage_error("error getting access token", parsed_response) 113 | return 114 | end 115 | 116 | # And save the `access_token` for future Manage API requests. Access tokens have a limited lifespan and will 117 | # eventually expire. The expiry date and time can be calulcated with the `expires_in` value from the response 118 | # (which is the lifepsan in seconds). You may want to save the `refresh_token` to refresh your access once the 119 | # access token expires. More information on refresh tokens can be found in the Clio API Documentation: 120 | # https://app.clio.com/api/v4/documentation#section/Authorization-with-OAuth-2.0/Oauth-Refresh-Tokens 121 | cookies.encrypted[:manage_token] = { 122 | value: parsed_response["access_token"], 123 | same_site: "None", 124 | secure: true, 125 | } 126 | 127 | # Now your user is fully authenticated and authorized! 128 | # 129 | # When a user adds your app to Clio Manage from the App Directory you are required to redirect them to a 130 | # Clio Manage callback page (not your applications dashboard). This ensures that the user returns back to 131 | # the App Directory where they started. At this point in time, after the authentication and authorization 132 | # is complete, you'd create their account within your application before issuing this redirect. You can 133 | # read more about this process in this support article: 134 | # https://developers.support.clio.com/hc/en-us/articles/4405283081627 135 | # 136 | # Rather than implementing SSO twice, once for the usual SSO login flow and once for the "Add to Clio" flow 137 | # from the App Directory, we use a query parameter and cookie to distinguish between the two. The "Add to Clio" 138 | # URL we would provide for this example app would look something like https://myapp.com/welcome?install_flow=1. 139 | # On the welcome page this query parameter is saved in a cookie and here we use it to determine next steps. 140 | # 141 | # If we are performing the "Add to Clio" flow we need to create the user and their account within our 142 | # application then redirect to the Clio Manage callback page. If we performing the usual SSO login flow 143 | # we need to redirect them to our apps dashboard, which in this example is the profile page. 144 | if cookies.encrypted[:install_flow] == "1" 145 | # Create user/account in your application here 146 | redirect_to ENV["CLIO_MANAGE_SITE_URL"] + "app_integrations_callback", allow_other_host: true 147 | else 148 | redirect_to "/auth_popup_callback" 149 | end 150 | end 151 | 152 | # Upon succesful callback, we send a message to the main window 153 | def auth_popup_callback 154 | render plain: "", content_type: "text/html" 155 | end 156 | 157 | def signout 158 | cookies.delete :manage_token 159 | cookies.delete :id_token 160 | cookies.delete :identity_state 161 | cookies.delete :manage_state 162 | redirect_to root_path 163 | end 164 | 165 | private 166 | 167 | def render_identity_error(message, info) 168 | render_error("Clio Identity authentication failed: " + message, info) 169 | end 170 | 171 | def render_manage_error(message, info) 172 | render_error("Clio Manage authorization failed: " + message, info) 173 | end 174 | 175 | def render_error(message, info) 176 | render "error", locals: { 177 | error_message: message, 178 | error_info: info 179 | } 180 | end 181 | 182 | def require_manage_token 183 | # Ensure we still have the tokens obtained during authentication and authorization. 184 | @id_token = cookies.encrypted[:id_token] 185 | @manage_token = cookies.encrypted[:manage_token] 186 | 187 | if @id_token.blank? || @manage_token.blank? 188 | redirect_to root_path 189 | end 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /app/controllers/matter_controller.rb: -------------------------------------------------------------------------------- 1 | require "http" 2 | 3 | class MatterController < ApplicationController 4 | 5 | before_action :require_manage_token 6 | 7 | def index 8 | # In this example we're getting the firms Matters using Unlimited Cursor Pagination 9 | # https://app.clio.com/api/v4/documentation#operation/Matter#index 10 | # https://app.clio.com/api/v4/documentation#section/Paging/Unlimited-Cursor-Pagination 11 | params = { 12 | fields: "id,display_number,description,status", 13 | status: "open", 14 | limit: 10, 15 | order: "id(asc)" 16 | } 17 | response = HTTP.auth("Bearer " + @manage_token).get(ENV["CLIO_MANAGE_SITE_URL"] + "api/v4/matters?" + params.to_query) 18 | 19 | handle_matters_response(response) 20 | end 21 | 22 | def paginate 23 | new_page_url = params[:page] 24 | response = HTTP.auth("Bearer " + @manage_token).get(new_page_url) 25 | handle_matters_response(response) 26 | end 27 | 28 | def show 29 | # In this example we're getting all attributes of a single Matter 30 | # https://app.clio.com/api/v4/documentation#operation/Matter#show 31 | matter_id = params[:id] 32 | if matter_id.blank? 33 | redirect_to root_path 34 | return 35 | end 36 | 37 | params = { 38 | fields: "id,etag,number,display_number,custom_number,description,status,location,client_reference,client_id,billable,maildrop_address,billing_method,open_date,close_date,pending_date,created_at,updated_at,shared,has_tasks,client,contingency_fee,custom_rate,evergreen_retainer,folder,group,matter_budget,originating_attorney,practice_area,responsible_attorney,statute_of_limitations,user,account_balances,custom_field_values,custom_field_set_associations,relationships,task_template_list_instances" 39 | } 40 | response = HTTP.auth("Bearer " + @manage_token).get(ENV["CLIO_MANAGE_SITE_URL"] + "api/v4/matters/" + matter_id + "?" + params.to_query) 41 | 42 | handle_matter_response(response) 43 | end 44 | 45 | private 46 | 47 | def handle_matters_response(response) 48 | parsed_response = JSON.parse(response) 49 | 50 | if response.code != 200 51 | render_error("Error getting matters response", parsed_response["error"]) 52 | return 53 | end 54 | 55 | render "index", locals: { 56 | paging: parsed_response["meta"]["paging"], 57 | matters: parsed_response["data"] 58 | } 59 | end 60 | 61 | def handle_matter_response(response) 62 | parsed_response = JSON.parse(response) 63 | 64 | if response.code != 200 65 | render_error("Error getting single matter response", parsed_response["error"]) 66 | return 67 | end 68 | 69 | render "show", locals: { 70 | matter: parsed_response["data"] 71 | } 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /app/controllers/profile_controller.rb: -------------------------------------------------------------------------------- 1 | require "http" 2 | 3 | class ProfileController < ApplicationController 4 | 5 | before_action :require_manage_token 6 | 7 | def index 8 | # Then use the Clio Manage Access Token to make an API call. In this example we're getting some basic profile 9 | # information from the `api/v4/users/who_am_i` endpoint and displaying it on our profile page. 10 | params = { 11 | fields: "id,first_name,last_name,email,phone_number,account_owner" 12 | } 13 | 14 | response = HTTP.auth("Bearer " + @manage_token).get(ENV["CLIO_MANAGE_SITE_URL"] + "api/v4/users/who_am_i?" + params.to_query) 15 | parsed_response = JSON.parse(response) 16 | 17 | if response.code != 200 18 | render_error("Error getting who_am_i? response", parsed_response["error"]) 19 | return 20 | end 21 | 22 | render "index", locals: { 23 | data: parsed_response["data"] 24 | } 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/welcome_controller.rb: -------------------------------------------------------------------------------- 1 | class WelcomeController < ApplicationController 2 | 3 | def index 4 | cookies.encrypted[:install_flow] = params[:install_flow] 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /app/helpers/identity_authentication_helper.rb: -------------------------------------------------------------------------------- 1 | module IdentityAuthenticationHelper 2 | class JsonWebKeyError < StandardError; end 3 | class JwtDecodeError < StandardError; end 4 | class InvalidTokenError < StandardError; end 5 | 6 | def identity_authenticate_url(state) 7 | params = { 8 | response_type: "code", 9 | client_id: ENV["CLIO_IDENTITY_CLIENT_ID"], 10 | redirect_uri: ENV["ROOT_URL"] + "identity_callback", 11 | scope: "openid", 12 | state: state 13 | } 14 | ENV["CLIO_IDENTITY_SITE_URL"] + "oauth2/auth?" + params.to_query 15 | end 16 | 17 | def identity_token_url 18 | ENV["CLIO_IDENTITY_SITE_URL"] + "oauth2/token" 19 | end 20 | 21 | def identity_token_body(code) 22 | { 23 | client_id: ENV["CLIO_IDENTITY_CLIENT_ID"], 24 | client_secret: ENV["CLIO_IDENTITY_CLIENT_SECRET"], 25 | grant_type: "authorization_code", 26 | code: code, 27 | redirect_uri: ENV["ROOT_URL"] + "identity_callback" 28 | } 29 | end 30 | 31 | def identity_state_valid?(identity_state) 32 | identity_state.present? && (identity_state == cookies.encrypted[:identity_state]) 33 | end 34 | 35 | def get_json_web_keys 36 | @public_keys ||= begin 37 | response = HTTP.get(ENV["CLIO_IDENTITY_SITE_URL"] + ".well-known/jwks.json") 38 | 39 | if response.code != 200 40 | raise JsonWebKeyError.new(response.body.to_s) 41 | end 42 | 43 | JSON.parse(response.body)["keys"] 44 | end 45 | end 46 | 47 | def decode_and_validate_identity_token(identity_token) 48 | json_web_keys = get_json_web_keys 49 | 50 | decode_options = { 51 | algorithm: "RS256", 52 | iss: ENV["CLIO_IDENTITY_SITE_URL"], 53 | verify_iss: true, 54 | aud: [ENV["CLIO_IDENTITY_CLIENT_ID"]], 55 | verify_aud: true, 56 | verify_iat: true, 57 | jwks: { 58 | keys: json_web_keys.map(&:symbolize_keys) 59 | } 60 | } 61 | 62 | decoded_token = JWT.decode(identity_token, nil, true, decode_options) 63 | 64 | if identity_token_valid?(decoded_token[0]) 65 | decoded_token 66 | else 67 | raise InvalidTokenError.new("token missing sid or has invalid azp value") 68 | end 69 | rescue JWT::DecodeError => e 70 | raise JwtDecodeError.new(e.message) 71 | end 72 | 73 | def identity_token_valid?(token) 74 | token["sid"].present? && 75 | (!token["azp"].present? || token["azp"] == ENV["CLIO_IDENTITY_CLIENT_ID"]) 76 | end 77 | 78 | end 79 | -------------------------------------------------------------------------------- /app/helpers/manage_authorization_helper.rb: -------------------------------------------------------------------------------- 1 | module ManageAuthorizationHelper 2 | 3 | def manage_authorize_url(state) 4 | params = { 5 | response_type: "code", 6 | client_id: ENV["CLIO_MANAGE_CLIENT_ID"], 7 | redirect_uri: ENV["ROOT_URL"] + "manage_callback", 8 | state: state, 9 | redirect_on_decline: true 10 | } 11 | ENV["CLIO_MANAGE_SITE_URL"] + "oauth/authorize?" + params.to_query 12 | end 13 | 14 | def manage_token_url 15 | ENV["CLIO_MANAGE_SITE_URL"] + "oauth/token?" 16 | end 17 | 18 | def manage_token_body(code) 19 | { 20 | client_id: ENV["CLIO_MANAGE_CLIENT_ID"], 21 | client_secret: ENV["CLIO_MANAGE_CLIENT_SECRET"], 22 | grant_type: "authorization_code", 23 | code: code, 24 | redirect_uri: ENV["ROOT_URL"] + "manage_callback" 25 | } 26 | end 27 | 28 | def manage_state_valid?(manage_state) 29 | manage_state.present? && manage_state == cookies.encrypted[:manage_state] 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/views/application/error.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Oops! Something went wrong.

5 | 6 | Back to Sign In 7 | 8 |
9 |
10 |

<%= error_message %>

11 | 12 | <% if error_info.present? %> 13 |

Error Info:

14 |
<%= JSON.pretty_generate(error_info) %>
15 | <% end %> 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Switchboard 5 | <%= csrf_meta_tags %> 6 | 7 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" %> 8 | <%= javascript_include_tag "application", "data-turbolinks-track": "reload" %> 9 | 10 | 11 | 12 | <%= yield %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/views/matter/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 14 |
15 |
16 |

Open Matters

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <% matters.each do |matter| %> 26 | 27 | 28 | 29 | 30 | 35 | 36 | <% end %> 37 |
Display NumberDescriptionStatus
<%= matter["display_number"] %><%= matter["description"] %><%= matter["status"] %> 31 | " class="button"> 32 | View 33 | 34 |
38 | 39 | 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /app/views/matter/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 16 |
17 |

<%= matter["display_number"] %>

18 | 19 | <% matter.without("display_number").select do |attribute_key, value| %> 20 | 21 | 22 | 41 | 42 | <% end %> 43 |
<%= attribute_key %> 23 | <% if value.is_a?(Hash) %> 24 |
25 | <% value.each do |sub_attribute_key, sub_value| %> 26 | <%= sub_attribute_key %>: <%= sub_value %>
27 | <% end %> 28 |
29 | <% elsif value.is_a?(Array) %> 30 |
31 | <% value.each do |object| %> 32 | <% object.each do |sub_attribute_key, sub_value| %> 33 | <%= sub_attribute_key %>: <%= sub_value %>
34 | <% end %> 35 |
36 | <% end %> 37 | <% else %> 38 | <%= value %> 39 | <% end %> 40 |
44 |
45 | <%= link_to "Back", :back, class: "button" %> 46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /app/views/profile/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 14 |
15 |
16 |

Profile

17 | 18 |

Hi <%= data["first_name"] %>! You have logged in to this example third-party application with single sign-on using your Clio account!

19 | 20 |
21 |

Your Clio Identity ID Token

22 |
<%= JSON.pretty_generate(cookies.encrypted[:id_token]) %>
23 |
24 | 25 |
26 |

Your Clio Manage Access Token

27 |
<%= cookies.encrypted[:manage_token] %>
28 |
29 | 30 |
31 |

Manage GET api/v4/users/who_am_i?

32 |
<%= JSON.pretty_generate(data) %>
33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /app/views/welcome/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |

Sign in to Switchboard

8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 |
19 |
20 | 26 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 3 | load Gem.bin_path("bundler", "bundle") 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | load File.expand_path("spring", __dir__) 3 | APP_PATH = File.expand_path("../config/application", __dir__) 4 | require_relative "../config/boot" 5 | require "rails/commands" 6 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | load File.expand_path("spring", __dir__) 3 | require_relative "../config/boot" 4 | require "rake" 5 | Rake.application.run 6 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | puts "\n== Removing old logs and tempfiles ==" 21 | system! "bin/rails log:clear tmp:clear" 22 | 23 | puts "\n== Restarting application server ==" 24 | system! "bin/rails restart" 25 | end 26 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) 3 | gem "bundler" 4 | require "bundler" 5 | 6 | # Load Spring without loading other gems in the Gemfile, for speed. 7 | Bundler.locked_gems&.specs&.find { |spec| spec.name == "spring" }&.tap do |spring| 8 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 9 | gem "spring", spring.version 10 | require "spring/binstub" 11 | rescue Gem::LoadError 12 | # Ignore when Spring is not installed. 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | # require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | require "action_cable/engine" 15 | require "rails/test_unit/railtie" 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module ExampleThirdPartyIntegrationApp 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 7.0 25 | 26 | # Configuration for the application, engines, and railties goes here. 27 | # 28 | # These settings can be overridden in specific environments using the files 29 | # in config/environments, which are processed later. 30 | # 31 | # config.time_zone = "Central Time (US & Canada)" 32 | # config.eager_load_paths << Rails.root.join("extras") 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | config.hosts << "example-third-party-application.clio.dev" 7 | 8 | # In the development environment your application's code is reloaded any time 9 | # it changes. This slows down response time but is perfect for development 10 | # since you don't have to restart the web server when you make code changes. 11 | config.cache_classes = false 12 | 13 | # Do not eager load code on boot. 14 | config.eager_load = false 15 | 16 | # Show full error reports. 17 | config.consider_all_requests_local = true 18 | 19 | # Enable server timing 20 | config.server_timing = true 21 | 22 | # Enable/disable caching. By default caching is disabled. 23 | # Run rails dev:cache to toggle caching. 24 | if Rails.root.join("tmp/caching-dev.txt").exist? 25 | config.action_controller.perform_caching = true 26 | config.action_controller.enable_fragment_cache_logging = true 27 | 28 | config.cache_store = :memory_store 29 | config.public_file_server.headers = { 30 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 31 | } 32 | else 33 | config.action_controller.perform_caching = false 34 | 35 | config.cache_store = :null_store 36 | end 37 | 38 | # Don't care if the mailer can't send. 39 | config.action_mailer.raise_delivery_errors = false 40 | 41 | config.action_mailer.perform_caching = false 42 | 43 | # Print deprecation notices to the Rails logger. 44 | config.active_support.deprecation = :log 45 | 46 | # Raise exceptions for disallowed deprecations. 47 | config.active_support.disallowed_deprecation = :raise 48 | 49 | # Tell Active Support which deprecation messages to disallow. 50 | config.active_support.disallowed_deprecation_warnings = [] 51 | 52 | # Suppress logger output for asset requests. 53 | config.assets.quiet = true 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | 61 | # Uncomment if you wish to allow Action Cable access from any origin. 62 | # config.action_cable.disable_request_forgery_protection = true 63 | end 64 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 12 | config.cache_classes = true 13 | 14 | # Eager loading loads your whole application. When running a single test locally, 15 | # this probably isn't necessary. It's a good idea to do in a continuous integration 16 | # system, or in some way before deploying your code. 17 | config.eager_load = ENV["CI"].present? 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Tell Action Mailer not to deliver emails to the real world. 39 | # The :test delivery method accumulates sent emails in the 40 | # ActionMailer::Base.deliveries array. 41 | config.action_mailer.delivery_method = :test 42 | 43 | # Print deprecation notices to the stderr. 44 | config.active_support.deprecation = :stderr 45 | 46 | # Raise exceptions for disallowed deprecations. 47 | config.active_support.disallowed_deprecation = :raise 48 | 49 | # Tell Active Support which deprecation messages to disallow. 50 | config.active_support.disallowed_deprecation_warnings = [] 51 | 52 | # Raises error for missing translations. 53 | # config.i18n.raise_on_missing_translations = true 54 | 55 | # Annotate rendered view with file names. 56 | # config.action_view.annotate_rendered_view_with_filenames = true 57 | end 58 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :password, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 6 | 7 | # Enable per-form CSRF tokens. Previous versions had false. 8 | Rails.application.config.action_controller.per_form_csrf_tokens = true 9 | 10 | # Enable origin-checking CSRF mitigation. Previous versions had false. 11 | Rails.application.config.action_controller.forgery_protection_origin_check = true 12 | 13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 14 | # Previous versions had false. 15 | ActiveSupport.to_time_preserves_timezone = true 16 | 17 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 18 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 19 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /config/initializers/secure_headers.rb: -------------------------------------------------------------------------------- 1 | SecureHeaders::Configuration.default do |config| 2 | config.csp = { 3 | default_src: %w('none'), # nothing allowed by default 4 | script_src: %w('self' 'unsafe-inline' 'unsafe-eval'), 5 | connect_src: %w('self'), 6 | img_src: %w('self' data:), 7 | font_src: %w('self' data:), 8 | base_uri: %w('self'), 9 | style_src: %w('unsafe-inline' 'self'), 10 | form_action: %w('self'), 11 | frame_ancestors: %w(*.app.clio.com app.clio.com app.myclio.ca:3000), 12 | } 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: "_example-third-party-integration-app_session" 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 3 | root "welcome#index" 4 | 5 | resource controller: "application", only: [] do 6 | get "authenticate_with_identity" 7 | get "identity_callback" 8 | get "manage_callback" 9 | get "auth_popup_callback" 10 | get "signout" 11 | end 12 | 13 | resources :welcome, only: [:index] 14 | resources :profile, only: [:index] 15 | get "/matter/paginate", to: "matter#paginate" 16 | resources :matter, only: [:index, :show] 17 | end 18 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 6263369ad0896abf78c5cde0ed359251ad8a37c50798ff6a847f2357058ecb0b8f0e413b8d9caca1540fd39d359c2e284a1ed553b6cdf8bf2f2c604816d68f71 15 | 16 | test: 17 | secret_key_base: 9b010ad7fffd726d74a942f47136ecb06127329665352de4d59cb14f496241046a6f7d44b3bbb0ed71981b706b56c90a48e266ec6fbefc2180e8996ec77972d3 18 | 19 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | sb_app: 4 | build: . 5 | command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3013 -b '0.0.0.0'" 6 | image: "example-third-party-app" 7 | volumes: 8 | - .:/Switchboard 9 | ports: 10 | - "3013:3013" 11 | extra_hosts: 12 | - "app.myclio.ca:${DOCKER_HOST_IP:-127.0.0.1}" 13 | - "account.myclio.ca:${DOCKER_HOST_IP:-127.0.0.1}" 14 | -------------------------------------------------------------------------------- /docs/docker_setup.md: -------------------------------------------------------------------------------- 1 | ## Docker Setup 2 | 3 | Before continuing with this setup, ensure that you have your valid API key pairs for both Clio Manage and Clio Identity, have cloned the example application repository and updated the environment variables as outlined in the [readme](../README.md). 4 | 5 | ### Docker 6 | 7 | Install Docker from [docs.docker.com/get-docker](https://docs.docker.com/get-docker/) or via brew: `brew install docker`. 8 | 9 | ### Start 10 | 11 | Now that you have Docker installed you can build the image: `docker compose build`. 12 | 13 | And boot the app: `docker compose up`. 14 | 15 | The example third-party application should now be running at http://localhost:3013/. 16 | -------------------------------------------------------------------------------- /docs/manual_setup.md: -------------------------------------------------------------------------------- 1 | ## Manual Setup 2 | 3 | Before continuing with this setup, ensure that you have your valid API key pairs for both Clio Manage and Clio Identity, have cloned the example application repository and updated the environment variables as outlined in the [readme](../README.md). 4 | 5 | ### Ruby and Rails 6 | 7 | This application utilizes Ruby `3.1.6` and Rails `7.0.8.4`. These can be installed through a variety of methods using different package managers, choose the right one for your system and preferences. 8 | 9 | ### Start 10 | 11 | Now that you have Ruby and Rails installed you can run the install bundle: 12 | 13 | ``` 14 | bundle install 15 | ``` 16 | 17 | Run the application: 18 | 19 | ``` 20 | bundle exec rails s -p 3013 21 | ``` 22 | 23 | The example third-party application should now be running at http://localhost:3013/. 24 | -------------------------------------------------------------------------------- /overlord.yml: -------------------------------------------------------------------------------- 1 | tier: 4 2 | owner: arian-kh -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/clio-logo-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/clio-logo-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/clio-logo-white.svg: -------------------------------------------------------------------------------- 1 | 90DCD20C-8CE3-4615-BE97-1AB9B1AC80DB -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clio/example-third-party-application/e5260e0ac4cd402eb2b8b74feb54dd6f5b26d8a1/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/switchboard_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get and append the docker host IP to the .env file if it isn't there already. 4 | set -euo pipefail 5 | grep -q DOCKER_HOST_IP .env \ 6 | || ruby /usr/bin/resolve_docker_host.rb 7 | 8 | # This is to fix a Rails-specific issue that prevents the server from restarting when a certain server.pid file pre-exists. 9 | set -e 10 | rm -f /myapp/tmp/pids/server.pid 11 | exec "$@" 12 | -------------------------------------------------------------------------------- /scripts/resolve_docker_host.rb: -------------------------------------------------------------------------------- 1 | require "resolv" 2 | 3 | docker_host_ip = begin 4 | Resolv.getaddress("host.docker.internal") 5 | rescue 6 | "127.0.0.1" 7 | end 8 | 9 | File.write(".env", "DOCKER_HOST_IP=#{docker_host_ip}", mode: "a") 10 | -------------------------------------------------------------------------------- /test/controllers/welcome_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class WelcomeControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get welcome_index_url 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require File.expand_path("../../config/environment", __FILE__) 3 | require "rails/test_help" 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | --------------------------------------------------------------------------------