├── log └── .keep ├── public ├── .keep └── swagger │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ └── swagger-initializer.js ├── .ruby-version ├── certs └── .gitkeep ├── db ├── dump │ └── content.dump.freeze ├── migrate │ ├── 20171101161031_enable_uuid_extension.rb │ ├── 20200922150215_enable_btree_gin_extension.rb │ ├── 20161108105842_add_envelope_type_index_to_envelopes.rb │ ├── 20230515091128_add_last_verified_on_to_envelopes.rb │ ├── 20211207110948_add_options_to_query_logs.rb │ ├── 20220315122626_add_resource_publish_type_to_envelopes.rb │ ├── 20250925025616_add_enqueued_at_to_envelope_downloads.rb │ ├── 20200601094240_add_purged_at_to_envelopes.rb │ ├── 20160824224410_add_fts_to_envelopes.rb │ ├── 20180729125600_add_last_graph_indexed_at_to_envelopes.rb │ ├── 20220113141414_add_envelope_resource_id_to_description_sets.rb │ ├── 20250922224518_add_status_to_envelope_downloads.rb │ ├── 20161101121532_add_resource_type_to_envelopes.rb │ ├── 20191024081858_add_secured_to_envelope_communities.rb │ ├── 20250815032532_remove_deleted_envelope_resources.rb │ ├── 20220106130200_add_envelope_community_id_to_description_sets.rb │ ├── 20160505095021_add_community_to_envelopes.rb │ ├── 20160824194535_add_pg_trgm_extension.rb │ ├── 20200813121714_add_secured_search_to_envelope_communities.rb │ ├── 20171215172051_add_secondary_publisher.rb │ ├── 20250618190719_add_publication_status_to_envelopes.rb │ ├── 20250829235024_remove_public_key_and_resource_from_envelopes.rb │ ├── 20250921174021_add_publication_status_to_versions.rb │ ├── 20171101152316_create_admins.rb │ ├── 20210624173908_change_query_logs_column_types.rb │ ├── 20250511180851_remove_not_null_from_resource_columns_in_envelopes.rb │ ├── 20250818021420_add_unique_index_on_envelope_id_and_resource_id_to_envelope_resources.rb │ ├── 20171121222132_move_key_pairs.rb │ ├── 20160527073357_add_object_changes_to_versions.rb │ ├── 20160414152951_create_administrative_accounts.rb │ ├── 20170312011508_create_json_schemas.rb │ ├── 20201012074942_add_publishing_organization_to_envelopes.rb │ ├── 20220314181045_add_indicies_for_bulk_purge_performance.rb │ ├── 20250618195306_add_publication_status_to_indexed_envelope_resources.rb │ ├── 20160524095936_create_envelope_transactions.rb │ ├── 20160824225705_add_gin_index_to_fts_trgm_on_envelopes.rb │ ├── 20170412045538_add_index_for_resource_id_on_envelopes.rb │ ├── 20171113221325_add_organization_and_publisher_references_to_envelopes.rb │ ├── 20240224174644_add_ocn_columns_to_envelope_communities.rb │ ├── 20160505094815_create_envelope_communities.rb │ ├── 20200727085544_create_description_sets.rb │ ├── 20220315190000_add_search_record_publish_type_to_indexed_envelope_resources.rb │ ├── 20180725215953_create_json_contexts.rb │ ├── 20210121082610_create_envelope_community_configs.rb │ ├── 20171101205513_create_auth_tokens.rb │ ├── 20210311135955_change_envelope_foreign_keys_on_delete.rb │ ├── 20230126122421_include_envelope_community_id_into_description_set_index.rb │ ├── 20210715141032_create_publish_requests.rb │ ├── 20210601020245_create_query_logs.rb │ ├── 20160407152817_create_versions.rb │ ├── 20230703110903_create_envelope_downloads.rb │ ├── 20171109230956_create_key_pairs.rb │ ├── 20171104152617_create_organization_publishers.rb │ ├── 20181121213645_change_envelope_resources_index.rb │ ├── 20181001205658_add_envelope_ceterms_ctid_and_envelope_ctdl_type_to_enveloeps.rb │ ├── 20171101211441_create_organizations.rb │ ├── 20171101194708_create_users.rb │ ├── 20171101194114_create_publishers.rb │ ├── 20160825034410_add_tsvector_to_envelopes.rb │ ├── 20160223171632_create_envelopes.rb │ ├── 20250902034147_add_envelope_ceterms_ctid_and_envelope_community_id_to_versions.rb │ ├── 20190227225740_fix_unique_ctid_index_for_envelopes.rb │ ├── 20180301172831_add_ctid_to_organizations.rb │ ├── 20250830180848_add_unique_index_on_envelope_community_id_to_envelope_downloads.rb │ ├── 20190919121231_change_scope_of_ctid_index_in_envelopes.rb │ ├── 20180727234351_remove_fts_from_envelopes.rb │ └── 20210513043719_create_indexed_envelope_resource_references.rb └── seeds.rb ├── .rspec ├── Procfile ├── config ├── boot.rb ├── environment.rb ├── envelope_communities.json ├── sidekiq.yml ├── dotenv_load.rb ├── newrelic.yml ├── authorized_keys │ ├── ce_registry │ │ ├── alex.pem │ │ ├── max.pem │ │ ├── anderson.pem │ │ ├── rsaksida.pem │ │ ├── cer_key1.pem │ │ └── cer_key2.pem │ ├── ce_registry_dev │ │ ├── alex.pem │ │ ├── max.pem │ │ ├── anderson.pem │ │ ├── cer_key1.pem │ │ └── cer_key2.pem │ └── learning_registry │ │ ├── alex.pem │ │ ├── max.pem │ │ ├── anderson.pem │ │ └── mr_default.pem ├── attribute_normalizers.rb ├── airbrake_load.rb ├── init_sidekiq.rb ├── ar_tasks.rb ├── database.yml └── arel_nodes_cte.rb ├── app ├── models │ ├── super_publisher.rb │ ├── indexed_envelope_resource_reference.rb │ ├── admin.rb │ ├── administrative_account.rb │ ├── envelope_community_config.rb │ ├── terms_of_service.rb │ ├── digital_signature.rb │ ├── description_set.rb │ ├── organization_publisher.rb │ ├── extensions │ │ ├── resource_type.rb │ │ ├── transactionable_envelope.rb │ │ ├── ce_registry_resources.rb │ │ └── learning_registry_resources.rb │ ├── auth_token.rb │ ├── identity.rb │ ├── envelope_version.rb │ ├── envelope_download.rb │ ├── query_log.rb │ ├── authorized_key.rb │ ├── api_user.rb │ ├── query_condition.rb │ ├── json_context.rb │ ├── user.rb │ ├── organization.rb │ ├── registry_metadata.rb │ └── indexed_envelope_resource.rb ├── policies │ ├── user_policy.rb │ ├── publisher_policy.rb │ ├── application_policy.rb │ ├── json_context_policy.rb │ ├── organization_policy.rb │ ├── envelope_resource_policy.rb │ ├── envelope_community_policy.rb │ ├── envelope_community_config_policy.rb │ └── envelope_policy.rb ├── services │ ├── purge_envelopes.rb │ ├── convert_bnode_to_uri.rb │ ├── base_interactor.rb │ ├── fetch_graph_resources.rb │ ├── indexer_stats.rb │ ├── validate_api_key.rb │ ├── ctdl_subclasses_resolver.rb │ ├── build_node_headers.rb │ └── generate_envelope_dump.rb ├── api │ ├── entities │ │ ├── description_set.rb │ │ ├── envelope_community_config.rb │ │ ├── envelope_community.rb │ │ ├── description_set_group.rb │ │ ├── user.rb │ │ ├── payload_formatter.rb │ │ ├── json_context.rb │ │ ├── envelope_community_config_version.rb │ │ ├── envelope_event.rb │ │ ├── description_set_data.rb │ │ ├── organization.rb │ │ ├── publisher.rb │ │ ├── envelope_download.rb │ │ ├── version.rb │ │ └── node_headers.rb │ ├── v1 │ │ ├── home.rb │ │ ├── indexer.rb │ │ ├── ce_registry.rb │ │ ├── users.rb │ │ ├── envelope_communities.rb │ │ ├── indexed_resources.rb │ │ └── revisions.rb │ ├── base.rb │ └── v2 │ │ └── base.rb ├── jobs │ ├── export_to_ocn_job.rb │ ├── precalculate_description_sets_job.rb │ ├── delete_from_ocn_job.rb │ ├── extract_envelope_resources_job.rb │ ├── concerns │ │ └── deduplicatable.rb │ ├── download_envelopes_job.rb │ └── index_envelope_job.rb └── views │ └── page.html.erb ├── docs ├── image.png ├── samples │ ├── python │ │ └── jwt_encode.py │ └── lua │ │ └── jwt_encode.lua └── 99_developer_notes.md ├── .dockerignore ├── spec ├── factories │ ├── admins.rb │ ├── administrative_accounts.rb │ ├── envelope_transactions.rb │ ├── organization_publishers.rb │ ├── delete_envelope.rb │ ├── organizations.rb │ ├── authentications.rb │ ├── json_contexts.rb │ ├── users.rb │ ├── envelope_community_configs.rb │ ├── publishers.rb │ ├── description_sets.rb │ ├── envelope_communities.rb │ ├── auth_tokens.rb │ ├── envelope_resources.rb │ ├── registry_metadatas.rb │ ├── delete_tokens.rb │ ├── envelope_downloads.rb │ └── indexed_envelope_resources.rb ├── support │ ├── fixtures │ │ ├── transactions-dump.txt.gz │ │ └── json │ │ │ └── ce_registry │ │ │ └── organization │ │ │ └── 2_valid.json │ ├── secrets.rb │ ├── shared_contexts │ │ └── envelopes_with_url.rb │ └── custom_matchers │ │ └── be_bnode.rb ├── models │ ├── user_spec.rb │ └── auth_token_spec.rb ├── api │ └── v1 │ │ ├── home_spec.rb │ │ ├── ce_registry_spec.rb │ │ ├── shared_examples │ │ ├── auth.rb │ │ └── missing_envelope.rb │ │ └── revisions_spec.rb ├── services │ ├── purge_envelopes_spec.rb │ └── envelope_builder_spec.rb └── jobs │ ├── extract_envelope_resources_job_spec.rb │ ├── download_envelopes_job_spec.rb │ └── precalculate_description_sets_job_spec.rb ├── rpms ├── bison-3.8.2-9.el10.x86_64.rpm ├── readline-devel-8.2-11.el10.x86_64.rpm └── RPM-DEPENDENCIES.md ├── terraform ├── environments │ ├── eks │ │ ├── k8s-manifests-prod │ │ │ ├── app-namespace.yaml │ │ │ ├── app-service-account.yaml │ │ │ ├── elasticsearch-pvc.yaml │ │ │ ├── app-secrets.yaml │ │ │ ├── elasticsearch-svc.yaml │ │ │ ├── redis-configmap.yaml │ │ │ ├── external-secrets-operator.yaml │ │ │ ├── elasticsearch-headless-svc.yaml │ │ │ ├── newrelic-apm-enable.yaml │ │ │ ├── app-configmap.yaml │ │ │ ├── certificate.yaml │ │ │ ├── app-ingress.yaml │ │ │ ├── db-migrate-job.yaml │ │ │ ├── app-hpa.yaml │ │ │ └── debug-aws-pod.yaml │ │ ├── k8s-manifests-sandbox │ │ │ ├── app-namespace.yaml │ │ │ ├── app-service-account.yaml │ │ │ ├── opensearch-pvc.yaml │ │ │ ├── elasticsearch-pvc.yaml │ │ │ ├── elasticsearch-svc.yaml │ │ │ ├── redis-configmap.yaml │ │ │ ├── certificate.yaml │ │ │ ├── clusterissuer.yaml │ │ │ ├── app-secrets.yaml │ │ │ ├── elasticsearch-headless-svc.yaml │ │ │ ├── priorityclasses.yaml │ │ │ ├── app-hpa.yaml │ │ │ ├── newrelic-apm-enable.yaml │ │ │ ├── app-ingress.yaml │ │ │ ├── db-migrate-job.yaml │ │ │ ├── app-configmap.yaml │ │ │ ├── debug-aws-pod.yaml │ │ │ └── external-secrets-values.yaml │ │ ├── k8s-manifests-staging │ │ │ ├── app-namespace.yaml │ │ │ ├── app-service-account.yaml │ │ │ ├── storageclass-gp3.yaml │ │ │ ├── opensearch-pvc.yaml │ │ │ ├── elasticsearch-pvc.yaml │ │ │ ├── elasticsearch-svc.yaml │ │ │ ├── redis-configmap.yaml │ │ │ ├── certificate.yaml │ │ │ ├── clusterissuer.yaml │ │ │ ├── app-secrets.yaml │ │ │ ├── elasticsearch-headless-svc.yaml │ │ │ ├── newrelic-apm-enable.yaml │ │ │ ├── priorityclasses.yaml │ │ │ ├── app-hpa.yaml │ │ │ ├── img-inspect.yaml │ │ │ ├── app-ingress.yaml │ │ │ ├── app-configmap.yaml │ │ │ ├── db-migrate-job.yaml │ │ │ └── debug-aws-pod.yaml │ │ ├── backend.tf │ │ ├── providers.tf │ │ └── skooner │ │ │ └── certificate.yaml │ └── github-ci-oidc │ │ └── trust.json └── modules │ ├── secrets │ ├── outputs.tf │ ├── variables.tf │ └── main.tf │ ├── rds │ └── outputs.tf │ ├── ecr │ ├── outputs.tf │ └── variables.tf │ ├── envelope_graphs_s3 │ ├── outputs.tf │ ├── variables.tf │ └── main.tf │ ├── vpc │ ├── outputs.tf │ └── variables.tf │ └── eks │ ├── iamrole-for-eks-cluster.tf │ └── iamrole-for-eks-nodegroup.tf ├── lib ├── tasks │ ├── console.rake │ ├── environment.rake │ ├── goopen.rake │ ├── routes.rake │ ├── swagger.rake │ ├── search.rake │ └── json_contexts.rake ├── rdf_node.rb ├── mountable_api.rb └── nonascii_friendly_uri.rb ├── local_packages ├── grape-middleware-logger-2.4.0.gem └── grape-middleware-logger-master.zip ├── .gitmodules ├── .semgrepignore ├── .env.test ├── openssl.cnf ├── .env.docker ├── bin ├── console ├── jwt_encode ├── rake ├── rackup ├── rspec ├── overcommit ├── rubocop ├── sidekiq └── delete_resource ├── Rakefile ├── .cloud66 ├── install_swagger.sh ├── install_postgresql_client.sh ├── install_new_relic_agent.sh ├── log_files.yml.erb ├── manifest.yml ├── papertrail.sh └── deploy_hooks.yml ├── docker-entrypoint.sh ├── .codeclimate.yml ├── .github └── workflows │ └── dependency-review.yaml ├── fixtures ├── schemas │ ├── json_schema.json.erb │ ├── publish_envelope.json │ └── schemaorg │ │ ├── _creative_work.json.erb │ │ ├── _address.json.erb │ │ └── _person.json.erb └── configs │ └── learning_registry.json ├── sonar-project.properties ├── .env ├── Vagrantfile ├── docker-compose.test.yml ├── .overcommit.yml └── config.ru /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.7 2 | -------------------------------------------------------------------------------- /certs/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /db/dump/content.dump.freeze: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | sidekiq: bin/sidekiq -r ./config/application.rb 2 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | -------------------------------------------------------------------------------- /app/models/super_publisher.rb: -------------------------------------------------------------------------------- 1 | class SuperPublisher < Publisher 2 | end 3 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/docs/image.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .env* 2 | !.env 3 | 4 | /log/* 5 | !/log/.keep 6 | /tmp/ 7 | Dockerfile* 8 | docker-compose*.yml 9 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | ENV['RACK_ENV'] ||= 'development' 2 | 3 | require File.expand_path('application', __dir__) 4 | -------------------------------------------------------------------------------- /spec/factories/admins.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :admin do 3 | name { Faker::Company.name } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /public/swagger/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/public/swagger/favicon-16x16.png -------------------------------------------------------------------------------- /public/swagger/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/public/swagger/favicon-32x32.png -------------------------------------------------------------------------------- /config/envelope_communities.json: -------------------------------------------------------------------------------- 1 | { 2 | "lr-staging.learningtapestry.com": "ce_registry", 3 | "example.org": "ce_registry" 4 | } 5 | -------------------------------------------------------------------------------- /rpms/bison-3.8.2-9.el10.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/rpms/bison-3.8.2-9.el10.x86_64.rpm -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/app-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: credreg-prod 5 | 6 | -------------------------------------------------------------------------------- /rpms/readline-devel-8.2-11.el10.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/rpms/readline-devel-8.2-11.el10.x86_64.rpm -------------------------------------------------------------------------------- /lib/tasks/console.rake: -------------------------------------------------------------------------------- 1 | desc 'Starts an interactive console.' 2 | task console: :cer_environment do 3 | require 'irb' 4 | ARGV.clear 5 | IRB.start 6 | end 7 | -------------------------------------------------------------------------------- /spec/support/fixtures/transactions-dump.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/spec/support/fixtures/transactions-dump.txt.gz -------------------------------------------------------------------------------- /local_packages/grape-middleware-logger-2.4.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/local_packages/grape-middleware-logger-2.4.0.gem -------------------------------------------------------------------------------- /spec/factories/administrative_accounts.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :administrative_account do 3 | public_key { Secrets.public_key } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/envelope_transactions.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :envelope_transaction do 3 | status { :created } 4 | envelope 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/organization_publishers.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :organization_publisher do 3 | organization 4 | publisher 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/support/fixtures/json/ce_registry/organization/2_valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "ceterms:Agent", 3 | "@id": "urn:ctid:e9344c77-f5c0-42a8-a8e7-22393c9cfa5e" 4 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/grape-middleware-logger"] 2 | path = vendor/grape-middleware-logger 3 | url = https://github.com/soverin/grape-middleware-logger.git 4 | -------------------------------------------------------------------------------- /local_packages/grape-middleware-logger-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CredentialEngine/CredentialRegistry/HEAD/local_packages/grape-middleware-logger-master.zip -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | # Ignore vendored/static assets and generated files 2 | public/swagger/** 3 | public/**.min.js 4 | vendor/** 5 | node_modules/** 6 | tmp/** 7 | coverage/** 8 | -------------------------------------------------------------------------------- /app/models/indexed_envelope_resource_reference.rb: -------------------------------------------------------------------------------- 1 | # A join model for storing references between resources 2 | class IndexedEnvelopeResourceReference < ActiveRecord::Base 3 | end 4 | -------------------------------------------------------------------------------- /db/migrate/20171101161031_enable_uuid_extension.rb: -------------------------------------------------------------------------------- 1 | class EnableUuidExtension < ActiveRecord::Migration[4.2] 2 | def change 3 | enable_extension 'uuid-ossp' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/tasks/environment.rake: -------------------------------------------------------------------------------- 1 | desc 'Sets up the application environment.' 2 | task :cer_environment do 3 | require File.expand_path('../../config/environment', __dir__) 4 | end 5 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/app-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: credreg-sandbox 5 | labels: 6 | name: main-app -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/app-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: credreg-staging 5 | labels: 6 | name: main-app -------------------------------------------------------------------------------- /terraform/modules/secrets/outputs.tf: -------------------------------------------------------------------------------- 1 | output "secret_arn" { 2 | description = "ARN of the AWS Secrets Manager secret" 3 | value = aws_secretsmanager_secret.this.arn 4 | } 5 | -------------------------------------------------------------------------------- /spec/factories/delete_envelope.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :delete_envelope, parent: :delete_token do 3 | envelope_id { create(:envelope).envelope_id } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922150215_enable_btree_gin_extension.rb: -------------------------------------------------------------------------------- 1 | 2 | class EnableBtreeGinExtension < ActiveRecord::Migration[5.2] 3 | def change 4 | enable_extension 'btree_gin' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/organizations.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :organization do 3 | admin 4 | description { Faker::Lorem.sentence } 5 | name { Faker::Company.name } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: <%= (ENV['SIDEKIQ_CONCURRENCY'].presence || 10).to_i - (ENV['PRECALCULATE_DESCRIPTION_SETS_CONCURRENCY'].presence || 1).to_i - 1 %> 3 | :queues: 4 | - default 5 | -------------------------------------------------------------------------------- /spec/factories/authentications.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :authentication do 3 | provider { :google } 4 | publisher 5 | uid { Faker::Lorem.characters(number: 16) } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/json_contexts.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :json_context do 3 | add_attribute(:context) { JSON(Faker::Json.shallow_json) } 4 | url { Faker::Internet.url } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/policies/user_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for user API 4 | class UserPolicy < ApplicationPolicy 5 | def create? 6 | user.admin? 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/services/purge_envelopes.rb: -------------------------------------------------------------------------------- 1 | # Physically deletes envelopes marked as purged 2 | class PurgeEnvelopes 3 | def self.call 4 | Envelope.where.not(purged_at: nil).find_each(&:destroy) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/goopen.rake: -------------------------------------------------------------------------------- 1 | namespace :goopen do 2 | desc 'Migrate LRv1 data for goopen' 3 | task migrate: :cer_environment do 4 | require 'goopen_migration' 5 | GoOpenMigration.migrate 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20161108105842_add_envelope_type_index_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddEnvelopeTypeIndexToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :envelopes, :envelope_type 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20230515091128_add_last_verified_on_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddLastVerifiedOnToEnvelopes < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :envelopes, :last_verified_on, :date 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/policies/publisher_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for publisher API 4 | class PublisherPolicy < ApplicationPolicy 5 | def create? 6 | user.admin? 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20211207110948_add_options_to_query_logs.rb: -------------------------------------------------------------------------------- 1 | class AddOptionsToQueryLogs < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :query_logs, :options, :jsonb, default: {}, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | POSTGRESQL_DATABASE=metadataregistry_test 2 | LOG_LEVEL=FATAL 3 | ENCRYPTED_PRIVATE_KEY_SECRET=fY+kmWiziK5yJf3hopz9Eha/FSCRYpzMkivn0fsVksbJVVDgTmw01zGUiAdeMus05yg1ULcqjpZ9nGvA6r5BeQ== 4 | REDIS_URL=redis://localhost:6379/3 5 | -------------------------------------------------------------------------------- /app/models/admin.rb: -------------------------------------------------------------------------------- 1 | # The account able to manage organization, publishers and their users 2 | class Admin < ActiveRecord::Base 3 | has_many :organizations 4 | has_many :publishers 5 | 6 | validates :name, presence: true 7 | end 8 | -------------------------------------------------------------------------------- /config/dotenv_load.rb: -------------------------------------------------------------------------------- 1 | if %w[development test].include?(ENV['RACK_ENV']) 2 | require 'dotenv' 3 | 4 | Dotenv.load( 5 | ".env.#{ENV['RACK_ENV']}.local", 6 | ".env.#{ENV['RACK_ENV']}", 7 | '.env' 8 | ) 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20220315122626_add_resource_publish_type_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddResourcePublishTypeToEnvelopes < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :envelopes, :resource_publish_type, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20250925025616_add_enqueued_at_to_envelope_downloads.rb: -------------------------------------------------------------------------------- 1 | class AddEnqueuedAtToEnvelopeDownloads < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :envelope_downloads, :enqueued_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200601094240_add_purged_at_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddPurgedAtToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :envelopes, :purged_at, :datetime 4 | add_index :envelopes, :purged_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/routes.rake: -------------------------------------------------------------------------------- 1 | desc 'API Routes' 2 | task routes: [:cer_environment] do 3 | API::Base.routes.each do |api| 4 | method = api.request_method.ljust(10) 5 | path = api.path 6 | puts " #{method} #{path}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | email { Faker::Internet.email } 4 | publisher 5 | 6 | trait :admin_account do 7 | admin 8 | publisher { nil } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/administrative_account.rb: -------------------------------------------------------------------------------- 1 | # Stores an administrative public key that is entitled to update/delete any 2 | # envelope 3 | class AdministrativeAccount < ActiveRecord::Base 4 | validates :public_key, presence: true, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160824224410_add_fts_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddFtsToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | change_table(:envelopes) do |t| 4 | t.text :fts_tsearch 5 | t.text :fts_trigram 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20180729125600_add_last_graph_indexed_at_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddLastGraphIndexedAtToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :envelopes, :last_graph_indexed_at, :datetime, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220113141414_add_envelope_resource_id_to_description_sets.rb: -------------------------------------------------------------------------------- 1 | class AddEnvelopeResourceIdToDescriptionSets < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :description_sets, :envelope_resource_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20250922224518_add_status_to_envelope_downloads.rb: -------------------------------------------------------------------------------- 1 | class AddStatusToEnvelopeDownloads < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :envelope_downloads, :status, :string, default: 'pending', null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/envelope_community_configs.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :envelope_community_config do 3 | description { Faker::Lorem.sentence } 4 | envelope_community 5 | payload { JSON(Faker::Json.shallow_json) } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/policies/application_policy.rb: -------------------------------------------------------------------------------- 1 | # Base policy; other policies inherit from it 2 | class ApplicationPolicy 3 | attr_reader :record, :user 4 | 5 | def initialize(user, record) 6 | @record = record 7 | @user = user 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20161101121532_add_resource_type_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddResourceTypeToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | change_table(:envelopes) do |t| 4 | t.string :resource_type, index: true 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20191024081858_add_secured_to_envelope_communities.rb: -------------------------------------------------------------------------------- 1 | class AddSecuredToEnvelopeCommunities < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :envelope_communities, :secured, :boolean, default: false, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20250815032532_remove_deleted_envelope_resources.rb: -------------------------------------------------------------------------------- 1 | class RemoveDeletedEnvelopeResources < ActiveRecord::Migration[8.0] 2 | def up 3 | EnvelopeResource.joins(:envelope).where.not(envelopes: { deleted_at: nil }).delete_all 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /terraform/modules/rds/outputs.tf: -------------------------------------------------------------------------------- 1 | output "db_security_group_id" { 2 | value = aws_security_group.app_db_security_group_rds.id 3 | } 4 | 5 | output "db_endpoint" { 6 | value = var.enable_db_instance ? aws_db_instance.application_main_db[0].endpoint : null 7 | } 8 | -------------------------------------------------------------------------------- /app/api/entities/description_set.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for the description set 4 | class DescriptionSet < Grape::Entity 5 | expose :path 6 | expose :total 7 | expose :uris 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/rdf_node.rb: -------------------------------------------------------------------------------- 1 | # Monkey-patching this class in order to ensure bnodes are unique 2 | module RDF 3 | class Node # rubocop:todo Style/Documentation 4 | def initialize(_ = nil) 5 | @id = RDF::Util::UUID.generate(format: :compact) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /openssl.cnf: -------------------------------------------------------------------------------- 1 | openssl_conf = openssl_init 2 | 3 | [openssl_init] 4 | providers = provider_sect 5 | 6 | [provider_sect] 7 | default = default_sect 8 | legacy = legacy_sect 9 | 10 | [default_sect] 11 | activate = 1 12 | 13 | [legacy_sect] 14 | activate = 1 15 | -------------------------------------------------------------------------------- /db/migrate/20220106130200_add_envelope_community_id_to_description_sets.rb: -------------------------------------------------------------------------------- 1 | class AddEnvelopeCommunityIdToDescriptionSets < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :description_sets, :envelope_community_id, :integer, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/envelope_community_config.rb: -------------------------------------------------------------------------------- 1 | # Represents an envelope community's configuration 2 | class EnvelopeCommunityConfig < ActiveRecord::Base 3 | has_paper_trail 4 | 5 | belongs_to :envelope_community 6 | 7 | validates :description, :payload, presence: true 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20160505095021_add_community_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddCommunityToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | change_table(:envelopes) do |t| 4 | t.references :envelope_community, null: false, foreign_key: true 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160824194535_add_pg_trgm_extension.rb: -------------------------------------------------------------------------------- 1 | class AddPgTrgmExtension < ActiveRecord::Migration[4.2] 2 | def up 3 | execute 'CREATE EXTENSION IF NOT EXISTS pg_trgm;' 4 | end 5 | 6 | def down 7 | execute 'DROP EXTENSION pg_trgm;' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200813121714_add_secured_search_to_envelope_communities.rb: -------------------------------------------------------------------------------- 1 | class AddSecuredSearchToEnvelopeCommunities < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :envelope_communities, :secured_search, :boolean, default: false, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/api/entities/envelope_community_config.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for the envelope community config 4 | class EnvelopeCommunityConfig < Grape::Entity 5 | expose :description 6 | expose :payload 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/publishers.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :publisher do 3 | admin 4 | contact_info { Faker::Lorem.paragraph } 5 | description { Faker::Lorem.sentence } 6 | name { Faker::Company.name } 7 | super_publisher { false } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.env.docker: -------------------------------------------------------------------------------- 1 | POSTGRESQL_ADDRESS=db 2 | POSTGRESQL_DATABASE=cr_development 3 | POSTGRESQL_USERNAME=postgres 4 | POSTGRESQL_PASSWORD=postgres 5 | 6 | REDIS_URL=redis://redis:6379/1 7 | 8 | STATEMENT_TIMEOUT=300000 9 | SECRET_KEY_BASE=[secret key string for signed cookies] 10 | 11 | -------------------------------------------------------------------------------- /app/services/convert_bnode_to_uri.rb: -------------------------------------------------------------------------------- 1 | # Converts a blank node ID into a dummy URI 2 | class ConvertBnodeToUri 3 | def self.call(value) 4 | return value unless value.is_a?(String) && value.starts_with?('_:') 5 | 6 | "https://credreg.net/bnodes/#{value[2..]}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/policies/json_context_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for organization API 4 | class JsonContextPolicy < ApplicationPolicy 5 | def index? 6 | user.admin? 7 | end 8 | 9 | def create? 10 | user.admin? 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 4 | $LOAD_PATH.unshift(File.dirname(__FILE__), '..') 5 | 6 | require 'pry' 7 | require_relative '../config/environment' 8 | 9 | # open console with the environment already loaded 10 | binding.pry 11 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/app-service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: main-app-service-account 5 | annotations: 6 | eks.amazonaws.com/role-arn: "arn:aws:iam::996810415034:role/ce-registry-eks-application-irsa-role" 7 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/app-service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: main-app-service-account 5 | annotations: 6 | eks.amazonaws.com/role-arn: "arn:aws:iam::996810415034:role/ce-registry-eks-application-irsa-role" 7 | -------------------------------------------------------------------------------- /app/models/terms_of_service.rb: -------------------------------------------------------------------------------- 1 | # Represents the envelope fields related to terms of service 2 | class TermsOfService 3 | include Virtus.model 4 | include ActiveModel::Validations 5 | 6 | attribute :submission_tos, String 7 | 8 | validates :submission_tos, presence: true 9 | end 10 | -------------------------------------------------------------------------------- /app/policies/organization_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for organization API 4 | class OrganizationPolicy < ApplicationPolicy 5 | def create? 6 | user.admin? 7 | end 8 | 9 | def destroy? 10 | user.admin? 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | ENV['RACK_ENV'] ||= 'development' 2 | 3 | require_relative 'config/application' 4 | require_relative 'config/ar_tasks' 5 | 6 | Rake.add_rakelib 'lib/tasks' 7 | 8 | if ENV['RACK_ENV'] == 'development' 9 | require 'grape-raketasks' 10 | require 'grape-raketasks/tasks' 11 | end 12 | -------------------------------------------------------------------------------- /app/models/digital_signature.rb: -------------------------------------------------------------------------------- 1 | # Represents the envelope fields related to a digital signature 2 | class DigitalSignature 3 | include Virtus.model 4 | include ActiveModel::Validations 5 | 6 | attribute :key_location, [String] 7 | 8 | validates :key_location, presence: true 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20171215172051_add_secondary_publisher.rb: -------------------------------------------------------------------------------- 1 | class AddSecondaryPublisher < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :envelopes, :secondary_publisher_id, :uuid, index: true 4 | add_foreign_key :envelopes, :publishers, column: :secondary_publisher_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20250618190719_add_publication_status_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddPublicationStatusToEnvelopes < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :envelopes, :publication_status, :integer, default: 0, null: false 4 | add_index :envelopes, :publication_status 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20250829235024_remove_public_key_and_resource_from_envelopes.rb: -------------------------------------------------------------------------------- 1 | class RemovePublicKeyAndResourceFromEnvelopes < ActiveRecord::Migration[8.0] 2 | def change 3 | remove_column :envelopes, :resource, :text 4 | remove_column :envelopes, :resource_public_key, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20250921174021_add_publication_status_to_versions.rb: -------------------------------------------------------------------------------- 1 | class AddPublicationStatusToVersions < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :versions, :publication_status, :integer, default: 0, null: false 4 | add_index :versions, :publication_status 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /public/swagger/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /.cloud66/install_swagger.sh: -------------------------------------------------------------------------------- 1 | wget https://github.com/swagger-api/swagger-ui/archive/v3.0.5.zip 2 | unzip v3.0.5.zip 3 | mv swagger-ui-3.0.5/dist $STACK_PATH/public/swagger 4 | rm -rf swagger-ui-3.0.5/ 5 | rm v3.0.5.zip 6 | sed -i -e 's,http://petstore.swagger.io/v2,,g' $STACK_PATH/public/swagger/index.html 7 | -------------------------------------------------------------------------------- /db/migrate/20171101152316_create_admins.rb: -------------------------------------------------------------------------------- 1 | class CreateAdmins < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :admins do |t| 4 | t.string :name, null: false 5 | 6 | t.timestamps null: false 7 | 8 | t.index :name, unique: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/factories/description_sets.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :description_set do 3 | ceterms_ctid { Envelope.generate_ctid } 4 | path { Faker::Lorem.word } 5 | uris { [Faker::Internet.url] } 6 | envelope_community { nil } 7 | envelope_resource { nil } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /terraform/environments/eks/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | bucket = "terraform-state-o1r8" 4 | key = "eks-registry/tfstate" 5 | region = "us-east-1" 6 | encrypt = true 7 | dynamodb_table = "terraform-state-locks" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.cloud66/install_postgresql_client.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | add-apt-repository "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" 3 | wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 4 | apt-get update 5 | apt-get install postgresql-client-16 -y 6 | -------------------------------------------------------------------------------- /app/policies/envelope_resource_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for envelope resource APIs 4 | class EnvelopeResourcePolicy < ApplicationPolicy 5 | def index? 6 | user.reader? 7 | end 8 | 9 | def create? 10 | user.publisher? 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20210624173908_change_query_logs_column_types.rb: -------------------------------------------------------------------------------- 1 | class ChangeQueryLogsColumnTypes < ActiveRecord::Migration[5.2] 2 | def change 3 | change_column :query_logs, :ctdl, :text 4 | change_column :query_logs, :result, :text 5 | change_column :query_logs, :query_logic, :text 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20250511180851_remove_not_null_from_resource_columns_in_envelopes.rb: -------------------------------------------------------------------------------- 1 | class RemoveNotNullFromResourceColumnsInEnvelopes < ActiveRecord::Migration[8.0] 2 | def up 3 | change_column_null :envelopes, :resource, true 4 | change_column_null :envelopes, :resource_public_key, true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20250818021420_add_unique_index_on_envelope_id_and_resource_id_to_envelope_resources.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexOnEnvelopeIdAndResourceIdToEnvelopeResources < ActiveRecord::Migration[8.0] 2 | def change 3 | add_index :envelope_resources, %i[envelope_id resource_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /terraform/modules/ecr/outputs.tf: -------------------------------------------------------------------------------- 1 | output "repository_url" { 2 | description = "ECR repository URL" 3 | value = aws_ecr_repository.app_repo.repository_url 4 | } 5 | 6 | output "repository_arn" { 7 | description = "ECR repository ARN" 8 | value = aws_ecr_repository.app_repo.arn 9 | } 10 | -------------------------------------------------------------------------------- /app/api/entities/envelope_community.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for the envelope community 4 | class EnvelopeCommunity < Grape::Entity 5 | expose :name 6 | expose :default 7 | expose :secured 8 | expose :secured_search 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20171121222132_move_key_pairs.rb: -------------------------------------------------------------------------------- 1 | class MoveKeyPairs < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :key_pairs, :organization_publisher_id, :integer 4 | add_column :key_pairs, :organization_id, :uuid, index: true 5 | add_foreign_key :key_pairs, :organizations 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/description_set.rb: -------------------------------------------------------------------------------- 1 | # Represents a description set for a resource: 2 | # an array of the URIs of the related resources connected to the given resource 3 | # by a certain property path 4 | class DescriptionSet < ActiveRecord::Base 5 | belongs_to :envelope_community 6 | belongs_to :envelope_resource 7 | end 8 | -------------------------------------------------------------------------------- /app/models/organization_publisher.rb: -------------------------------------------------------------------------------- 1 | # Join model used for whitelisting publishers for certain organizations 2 | class OrganizationPublisher < ActiveRecord::Base 3 | belongs_to :organization 4 | belongs_to :publisher 5 | 6 | validates :organization, presence: true 7 | validates :publisher, presence: true 8 | end 9 | -------------------------------------------------------------------------------- /app/policies/envelope_community_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for organization API 4 | class EnvelopeCommunityPolicy < ApplicationPolicy 5 | def create? 6 | return true if user.superadmin? 7 | 8 | user.admin? && record == user.community 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/app-service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: main-app-service-account 5 | namespace: credreg-prod 6 | annotations: 7 | eks.amazonaws.com/role-arn: "arn:aws:iam::996810415034:role/ce-registry-eks-application-irsa-role" 8 | 9 | -------------------------------------------------------------------------------- /app/jobs/export_to_ocn_job.rb: -------------------------------------------------------------------------------- 1 | require 'ocn_exporter' 2 | 3 | class ExportToOCNJob < ActiveJob::Base # rubocop:todo Style/Documentation 4 | def perform(envelope_id) 5 | envelope = Envelope.find_by(id: envelope_id) 6 | return unless envelope 7 | 8 | OCNExporter.new(envelope:).export 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/policies/envelope_community_config_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for envelope community config API 4 | class EnvelopeCommunityConfigPolicy < ApplicationPolicy 5 | def create? 6 | user.admin? 7 | end 8 | 9 | def show? 10 | user.admin? 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20160527073357_add_object_changes_to_versions.rb: -------------------------------------------------------------------------------- 1 | class AddObjectChangesToVersions < ActiveRecord::Migration[4.2] 2 | # The largest text column available in all supported RDBMS. 3 | TEXT_BYTES = 1_073_741_823 4 | 5 | def change 6 | add_column :versions, :object_changes, :text, limit: TEXT_BYTES 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /terraform/modules/envelope_graphs_s3/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bucket_name" { 2 | value = aws_s3_bucket.this.bucket 3 | description = "S3 bucket name for envelope graphs" 4 | } 5 | 6 | output "bucket_arn" { 7 | value = aws_s3_bucket.this.arn 8 | description = "S3 bucket ARN for envelope graphs" 9 | } 10 | 11 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/storageclass-gp3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1 2 | kind: StorageClass 3 | metadata: 4 | name: gp3 5 | provisioner: ebs.csi.aws.com 6 | parameters: 7 | type: gp3 8 | reclaimPolicy: Delete 9 | volumeBindingMode: WaitForFirstConsumer 10 | allowVolumeExpansion: true 11 | 12 | -------------------------------------------------------------------------------- /db/migrate/20160414152951_create_administrative_accounts.rb: -------------------------------------------------------------------------------- 1 | class CreateAdministrativeAccounts < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :administrative_accounts do |t| 4 | t.string :public_key, null: false, index: { unique: true } 5 | 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/elasticsearch-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | namespace: credreg-prod 5 | name: elasticsearch-data 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 20Gi 12 | storageClassName: gp3 13 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/opensearch-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | namespace: credreg-sandbox 5 | name: opensearch-data 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 20Gi 12 | storageClassName: gp2 13 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/opensearch-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | namespace: credreg-staging 5 | name: opensearch-data 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 20Gi 12 | storageClassName: gp2 13 | -------------------------------------------------------------------------------- /terraform/environments/eks/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = ">= 5.50" 6 | } 7 | } 8 | } 9 | 10 | # Configure the AWS provider. Region can also be set via the AWS_REGION env var. 11 | provider "aws" { 12 | region = "us-east-1" 13 | } 14 | -------------------------------------------------------------------------------- /db/migrate/20170312011508_create_json_schemas.rb: -------------------------------------------------------------------------------- 1 | class CreateJsonSchemas < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :json_schemas do |t| 4 | t.string :name, null: false, index: true 5 | t.jsonb :schema, null: false, default: '{}' 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/elasticsearch-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | namespace: credreg-sandbox 5 | name: elasticsearch-data 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 20Gi 12 | storageClassName: gp3 13 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/elasticsearch-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | namespace: credreg-staging 5 | name: elasticsearch-data 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 20Gi 12 | storageClassName: gp3 13 | -------------------------------------------------------------------------------- /app/api/entities/description_set_group.rb: -------------------------------------------------------------------------------- 1 | require 'entities/description_set' 2 | 3 | module API 4 | module Entities 5 | # Presenter for description sets grouped by the CTID 6 | class DescriptionSetGroup < Grape::Entity 7 | expose :ctid 8 | expose :description_set, using: DescriptionSet 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20201012074942_add_publishing_organization_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddPublishingOrganizationToEnvelopes < ActiveRecord::Migration[5.2] 2 | def change 3 | add_reference :envelopes, 4 | :publishing_organization, 5 | foreign_key: { to_table: :organizations }, 6 | type: :uuid 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /rpms/RPM-DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | # RPM Packages dependencies 2 | 3 | 1. name: readline-devel 4 | source: https://rpmfind.net/linux/almalinux/10.0/AppStream/x86_64/os/Packages/readline-devel-8.2-11.el10.x86_64.rpm 5 | 6 | 2. name: bison 7 | source: https://repo.almalinux.org/almalinux/10/AppStream/x86_64/os/Packages/bison-3.8.2-9.el10.x86_64.rpm 8 | 9 | -------------------------------------------------------------------------------- /spec/factories/envelope_communities.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :envelope_community do 3 | name { 'learning_registry' } 4 | default { false } 5 | backup_item { 'learning-registry-test' } 6 | 7 | trait :with_random_name do 8 | name { Faker::Lorem.word } 9 | backup_item { "#{name}-test" } 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20220314181045_add_indicies_for_bulk_purge_performance.rb: -------------------------------------------------------------------------------- 1 | class AddIndiciesForBulkPurgePerformance < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :envelope_transactions, :envelope_id 4 | add_index :indexed_envelope_resource_references, :resource_id 5 | add_index :indexed_envelope_resource_references, :resource_uri 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20250618195306_add_publication_status_to_indexed_envelope_resources.rb: -------------------------------------------------------------------------------- 1 | class AddPublicationStatusToIndexedEnvelopeResources < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :indexed_envelope_resources, :publication_status, :integer, default: 0, null: false 4 | add_index :indexed_envelope_resources, :publication_status 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/support/secrets.rb: -------------------------------------------------------------------------------- 1 | class Secrets 2 | ENCRYPTED_PRIVATE_KEY_SECRET = SecureRandom.hex 3 | 4 | class << self 5 | def private_key 6 | rsa_key.to_pem 7 | end 8 | 9 | def public_key 10 | rsa_key.public_key.to_pem 11 | end 12 | 13 | def rsa_key 14 | @rsa_key ||= OpenSSL::PKey::RSA.new(2048) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /terraform/environments/eks/skooner/certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: skooner-certificate 5 | namespace: kube-system 6 | spec: 7 | secretName: skooner-tls 8 | issuerRef: 9 | name: letsencrypt-prod 10 | kind: ClusterIssuer 11 | dnsNames: 12 | - status.credentialengineregistry.org 13 | 14 | -------------------------------------------------------------------------------- /terraform/modules/ecr/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | description = "Environment (dev/stage/prod)" 3 | type = string 4 | } 5 | 6 | variable "project_name" { 7 | description = "Project name" 8 | type = string 9 | } 10 | 11 | variable "common_tags" { 12 | type = map(string) 13 | default = { 14 | "name" = "default value" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/app-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: app-secrets 5 | namespace: credreg-prod 6 | type: Opaque 7 | stringData: 8 | # Populate via External Secrets or CI; placeholders here 9 | SECRET_KEY_BASE: "" 10 | POSTGRESQL_ADDRESS: "" 11 | POSTGRESQL_PASSWORD: "" 12 | REDIS_URL: "" 13 | 14 | -------------------------------------------------------------------------------- /db/migrate/20160524095936_create_envelope_transactions.rb: -------------------------------------------------------------------------------- 1 | class CreateEnvelopeTransactions < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :envelope_transactions do |t| 4 | t.integer :status, null: false, default: 0 5 | t.references :envelope, null: false, foreign_key: true 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /.cloud66/install_new_relic_agent.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$CLOUD66_STACK_ENVIRONMENT" == "production" ]||[ "$CLOUD66_STACK_ENVIRONMENT" == "sandbox" ]; then 4 | curl -Ls https://download.newrelic.com/install/newrelic-cli/scripts/install.sh | bash && sudo NEW_RELIC_API_KEY=$NEW_RELIC_API_KEY NEW_RELIC_ACCOUNT_ID=$NEW_RELIC_ACCOUNT_ID /usr/local/bin/newrelic install -y 5 | fi 6 | -------------------------------------------------------------------------------- /db/migrate/20160824225705_add_gin_index_to_fts_trgm_on_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddGinIndexToFtsTrgmOnEnvelopes < ActiveRecord::Migration[4.2] 2 | def up 3 | execute('CREATE INDEX envelopes_fts_trigram_idx ON envelopes '\ 4 | 'USING gin (fts_trigram gin_trgm_ops)') 5 | end 6 | 7 | def down 8 | execute("DROP INDEX envelopes_fts_trigram_idx") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20170412045538_add_index_for_resource_id_on_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddIndexForResourceIdOnEnvelopes < ActiveRecord::Migration[4.2] 2 | def up 3 | execute('CREATE INDEX envelopes_resources_id_idx ON envelopes ' \ 4 | '((processed_resource->>\'@id\'));') 5 | end 6 | 7 | def down 8 | execute("DROP INDEX envelopes_resources_id_idx") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20171113221325_add_organization_and_publisher_references_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationAndPublisherReferencesToEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | change_table :envelopes do |t| 4 | t.references :organization, foreign_key: true, type: :uuid 5 | t.references :publisher, foreign_key: true, type: :uuid 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/elasticsearch-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: elasticsearch 5 | namespace: credreg-prod 6 | labels: 7 | app: elasticsearch 8 | spec: 9 | type: ClusterIP 10 | selector: 11 | app: elasticsearch 12 | ports: 13 | - name: http 14 | port: 9200 15 | targetPort: 9200 16 | 17 | -------------------------------------------------------------------------------- /.cloud66/log_files.yml.erb: -------------------------------------------------------------------------------- 1 | files: 2 | - <%= ENV['STACK_BASE'] %>/shared/log/production.log 3 | - <%= ENV['STACK_BASE'] %>/shared/log/staging.log 4 | - <%= ENV['STACK_BASE'] %>/shared/log/sandbox.log 5 | - <%= ENV['STACK_BASE'] %>/shared/log/nginx_error.log 6 | destination: 7 | host: <%= ENV['PAPERTRAIL_HOST'] %> 8 | port: <%= ENV['PAPERTRAIL_PORT'] %> 9 | protocol: tls 10 | -------------------------------------------------------------------------------- /bin/jwt_encode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Small script to encode some content using JWT 4 | # 5 | 6 | require 'jwt' 7 | 8 | if ARGV.length != 2 9 | puts 'USAGE: jwt_encode RESOURCE PRIVATE_KEY' 10 | exit 11 | end 12 | 13 | json_content = JSON.parse(File.read(ARGV[0])) 14 | key = OpenSSL::PKey::RSA.new(File.read(ARGV[1])) 15 | 16 | puts JWT.encode json_content, key, 'RS256' 17 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/elasticsearch-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: elasticsearch 5 | namespace: credreg-sandbox 6 | labels: 7 | app: elasticsearch 8 | spec: 9 | type: ClusterIP 10 | selector: 11 | app: elasticsearch 12 | ports: 13 | - name: http 14 | port: 9200 15 | targetPort: 9200 16 | 17 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/elasticsearch-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: elasticsearch 5 | namespace: credreg-staging 6 | labels: 7 | app: elasticsearch 8 | spec: 9 | type: ClusterIP 10 | selector: 11 | app: elasticsearch 12 | ports: 13 | - name: http 14 | port: 9200 15 | targetPort: 9200 16 | 17 | -------------------------------------------------------------------------------- /app/jobs/precalculate_description_sets_job.rb: -------------------------------------------------------------------------------- 1 | require 'precalculate_description_sets' 2 | 3 | class PrecalculateDescriptionSetsJob < ActiveJob::Base # rubocop:todo Style/Documentation 4 | queue_as :description_set 5 | 6 | def perform(envelope_id) 7 | envelope = Envelope.find_by(id: envelope_id) 8 | PrecalculateDescriptionSets.process(envelope) if envelope 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/mountable_api.rb: -------------------------------------------------------------------------------- 1 | # Workaround class that allows mounting the same API to different routes. 2 | # Check out https://github.com/ruby-grape/grape/issues/570 3 | class MountableAPI 4 | def self.api_class 5 | Class.new(Grape::API).tap do |klass| 6 | klass.instance_eval(&@proc) 7 | end 8 | end 9 | 10 | def self.mounted(&block) 11 | @proc = block 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /.cloud66/manifest.yml: -------------------------------------------------------------------------------- 1 | production: &production 2 | rack: 3 | configuration: 4 | custom_deploy_command: bin/rake db:migrate 5 | operating_system: ubuntu2204 6 | ruby_version: 3.4.3 7 | redis: 8 | configuration: 9 | version: 7.2.4 10 | 11 | development: 12 | <<: *production 13 | 14 | sandbox: 15 | <<: *production 16 | 17 | staging: 18 | <<: *production 19 | -------------------------------------------------------------------------------- /db/migrate/20240224174644_add_ocn_columns_to_envelope_communities.rb: -------------------------------------------------------------------------------- 1 | class AddOcnColumnsToEnvelopeCommunities < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :envelope_communities, :ocn_directory_id, :uuid 4 | add_column :envelope_communities, :ocn_export_enabled, :boolean, default: false, null: false 5 | add_column :envelope_communities, :ocn_s3_bucket, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/nonascii_friendly_uri.rb: -------------------------------------------------------------------------------- 1 | # https://stackoverflow.com/a/37599235/341470 2 | module URI 3 | DOUBLE_ESCAPED_REGEX = /%25([0-9a-f]{2})/i 4 | 5 | class << self 6 | alias parse_without_escape parse 7 | 8 | def parse(uri) 9 | escaped_uri = DEFAULT_PARSER.escape(uri) 10 | parse_without_escape(escaped_uri.gsub(DOUBLE_ESCAPED_REGEX, '%\1')) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/envelopes_with_url.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context 'envelopes with url' do # rubocop:todo RSpec/ContextWording 2 | let(:processed_resource) do 3 | url ||= 'http://example.org/resource' 4 | build(:resource, url: url) 5 | end 6 | 7 | let!(:envelopes) do # rubocop:todo RSpec/LetSetup 8 | Array.new(2) { create(:envelope, processed_resource:) } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/policies/envelope_policy.rb: -------------------------------------------------------------------------------- 1 | require_relative 'application_policy' 2 | 3 | # Specifies policies for envelopes APIs 4 | class EnvelopePolicy < ApplicationPolicy 5 | def index? 6 | user.publisher? 7 | end 8 | 9 | def create? 10 | user.publisher? 11 | end 12 | 13 | def update? 14 | user.publisher? 15 | end 16 | 17 | def destroy? 18 | user.publisher? 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/extensions/resource_type.rb: -------------------------------------------------------------------------------- 1 | # Assigns a resource type to an envelope or envelope resource 2 | module ResourceType 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | before_save :set_resource_type 7 | 8 | def set_resource_type 9 | return unless resource_data? 10 | 11 | self.resource_type = envelope_community&.resource_type_for(self) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20160505094815_create_envelope_communities.rb: -------------------------------------------------------------------------------- 1 | class CreateEnvelopeCommunities < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :envelope_communities do |t| 4 | t.string :name, null: false, index: { unique: true } 5 | t.boolean :default, null: false, default: false 6 | t.string :backup_item 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200727085544_create_description_sets.rb: -------------------------------------------------------------------------------- 1 | class CreateDescriptionSets < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :description_sets do |t| 4 | t.string :ceterms_ctid, null: false 5 | t.string :path, null: false 6 | t.string :uris, array: true, default: [], null: false 7 | 8 | t.index %i[ceterms_ctid path], unique: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20220315190000_add_search_record_publish_type_to_indexed_envelope_resources.rb: -------------------------------------------------------------------------------- 1 | class AddSearchRecordPublishTypeToIndexedEnvelopeResources < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :indexed_envelope_resources, :"search:resourcePublishType", :string 4 | add_index :indexed_envelope_resources, :"search:resourcePublishType", name: "i_ctdl_search_resourcePublishType" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/auth_token.rb: -------------------------------------------------------------------------------- 1 | require 'user' 2 | 3 | # Stores values for token-based authentication 4 | class AuthToken < ActiveRecord::Base 5 | belongs_to :user 6 | has_one :admin, through: :user 7 | 8 | before_create :generate_value 9 | 10 | def generate_value 11 | loop do 12 | self.value = SecureRandom.hex 13 | break unless AuthToken.exists?(value: value) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20180725215953_create_json_contexts.rb: -------------------------------------------------------------------------------- 1 | class CreateJsonContexts < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :json_contexts do |t| 4 | t.string :url, null: false 5 | t.jsonb :context, null: false 6 | t.timestamps null: false 7 | end 8 | 9 | add_index :json_contexts, :url, unique: true 10 | add_index :json_contexts, :context, using: :gin 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20210121082610_create_envelope_community_configs.rb: -------------------------------------------------------------------------------- 1 | class CreateEnvelopeCommunityConfigs < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :envelope_community_configs do |t| 4 | t.string :description, null: false 5 | t.references :envelope_community, foreign_key: true, index: true, null: false 6 | t.jsonb :payload, default: '{}', null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /terraform/modules/vpc/outputs.tf: -------------------------------------------------------------------------------- 1 | # modules/vpc/outputs.tf 2 | output "vpc_id" { 3 | value = aws_vpc.app_vpc.id 4 | 5 | } 6 | output "private_subnet_ids" { 7 | description = "IDs of the private subnets" 8 | value = aws_subnet.private_app_vpc[*].id 9 | } 10 | 11 | output "public_subnet_ids" { 12 | description = "IDs of the public subnets" 13 | value = aws_subnet.public_app_vpc[*].id 14 | } 15 | 16 | -------------------------------------------------------------------------------- /app/api/entities/user.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for users 4 | class User < Grape::Entity 5 | expose :id, 6 | documentation: { type: 'string', 7 | desc: 'Unique identifier' } 8 | expose :email, 9 | documentation: { type: 'string', 10 | desc: 'Email of this user' } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/jobs/delete_from_ocn_job.rb: -------------------------------------------------------------------------------- 1 | require 'ocn_exporter' 2 | 3 | class DeleteFromOCNJob < ActiveJob::Base # rubocop:todo Style/Documentation 4 | def perform(envelope_ceterms_ctid, envelope_community_id) 5 | envelope_community = EnvelopeCommunity.find(envelope_community_id) 6 | envelope = Envelope.new(envelope_ceterms_ctid:, envelope_community:) 7 | 8 | OCNExporter.new(envelope:).delete_from_s3 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/models/identity.rb: -------------------------------------------------------------------------------- 1 | # Represents the envelope fields related to identity 2 | class Identity 3 | include Virtus.model 4 | include ActiveModel::Validations 5 | 6 | attribute :submitter_type, String 7 | attribute :submitter, String 8 | attribute :signer, String 9 | 10 | validates :submitter_type, :submitter, presence: true 11 | validates :submitter_type, inclusion: { in: %w[anonymous user agent] } 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20171101205513_create_auth_tokens.rb: -------------------------------------------------------------------------------- 1 | class CreateAuthTokens < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :auth_tokens do |t| 4 | t.integer :user_id 5 | t.string :value, null: false 6 | 7 | t.timestamps null: false 8 | 9 | t.index :user_id 10 | t.index :value, unique: true 11 | 12 | t.foreign_key :users, on_delete: :cascade 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe User do 2 | describe 'after_create' do 3 | it 'creates auth token' do 4 | user = described_class.new( 5 | email: Faker::Internet.email, 6 | publisher: create(:publisher) 7 | ) 8 | expect { user.save! }.to change { user.auth_tokens.count }.by(1) 9 | expect { user.save! }.not_to change { user.auth_tokens.count } 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/redis-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: redis-config 5 | data: 6 | redis.conf: | 7 | bind 0.0.0.0 8 | port 6379 9 | requirepass your_secure_password 10 | appendonly yes 11 | maxmemory 500mb 12 | maxmemory-policy allkeys-lru 13 | tcp-keepalive 300 14 | protected-mode yes 15 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/redis-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: redis-config 5 | data: 6 | redis.conf: | 7 | bind 0.0.0.0 8 | port 6379 9 | requirepass your_secure_password 10 | appendonly yes 11 | maxmemory 500mb 12 | maxmemory-policy allkeys-lru 13 | tcp-keepalive 300 14 | protected-mode yes 15 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: sandbox-credentialengineregistry-org-tls 5 | namespace: credreg-sandbox 6 | spec: 7 | secretName: sandbox-credentialengineregistry-org-tls 8 | dnsNames: 9 | - sandbox.credentialengineregistry.org 10 | issuerRef: 11 | name: letsencrypt-prod 12 | kind: ClusterIssuer -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: staging-credentialengineregistry-org-tls 5 | namespace: credreg-staging 6 | spec: 7 | secretName: staging-credentialengineregistry-org-tls 8 | dnsNames: 9 | - staging.credentialengineregistry.org 10 | issuerRef: 11 | name: letsencrypt-prod 12 | kind: ClusterIssuer -------------------------------------------------------------------------------- /app/services/base_interactor.rb: -------------------------------------------------------------------------------- 1 | # Base class for interactors 2 | class BaseInteractor 3 | attr_reader :error 4 | 5 | def self.call(params) 6 | interactor = new(params) 7 | interactor.call(params) 8 | interactor 9 | end 10 | 11 | def initialize(params) 12 | @params = params 13 | end 14 | 15 | def call 16 | raise NotImplementedError 17 | end 18 | 19 | def success? 20 | @error.nil? 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /terraform/modules/envelope_graphs_s3/variables.tf: -------------------------------------------------------------------------------- 1 | variable "bucket_name" { 2 | description = "Name of the S3 bucket" 3 | type = string 4 | } 5 | 6 | variable "environment" { 7 | description = "Environment name (e.g., staging, production)" 8 | type = string 9 | } 10 | 11 | variable "common_tags" { 12 | description = "Common tags to apply to resources" 13 | type = map(string) 14 | default = {} 15 | } 16 | 17 | -------------------------------------------------------------------------------- /app/api/v1/home.rb: -------------------------------------------------------------------------------- 1 | require 'exceptions' 2 | require 'v1/envelopes' 3 | require 'render_markdown' 4 | 5 | module API 6 | module V1 7 | # Home page 8 | class Home < Grape::API 9 | # return HTML instead of json 10 | content_type :html, 'text/html' 11 | format :html 12 | 13 | desc 'Homepage' 14 | get '/readme' do 15 | RenderMarkdown.new('README').to_html 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Ensure app log directory and files exist (avoid EACCES/ENOENT) 5 | mkdir -p /app/log || true 6 | touch /app/log/production.log /app/log/newrelic_agent.log || true 7 | 8 | if [ -z "${SECRET_KEY_BASE}" ] || [ "${SECRET_KEY_BASE}" = "dummy-value" ]; then 9 | export SECRET_KEY_BASE="$(openssl rand -hex 32)" 10 | echo "[entrypoint] Generated new SECRET_KEY_BASE" >&2 11 | fi 12 | 13 | exec "$@" 14 | -------------------------------------------------------------------------------- /lib/tasks/swagger.rake: -------------------------------------------------------------------------------- 1 | namespace :swagger do 2 | desc 'Build swagger json' 3 | task build: :cer_environment do 4 | require 'swagger_docs' 5 | 6 | path = File.expand_path('../../tmp/swagger.json', __dir__) 7 | File.open(path, 'w') do |f| 8 | puts "Writing swagger definition to #{path}" 9 | definition = Swagger::Blocks.build_root_json [MR::SwaggerDocs] 10 | f.write definition.to_json 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/api/entities/payload_formatter.rb: -------------------------------------------------------------------------------- 1 | # Adds a method for formatting payload contents. 2 | module PayloadFormatter 3 | def format_payload(payload) 4 | new_payload = {} 5 | ['@context', '@id', '@graph'].each do |key| 6 | val = payload.delete(key) 7 | new_payload[key] = val if val.present? 8 | end 9 | payload.each { |k, v| new_payload[k] = v } 10 | new_payload 11 | end 12 | 13 | module_function :format_payload 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20210311135955_change_envelope_foreign_keys_on_delete.rb: -------------------------------------------------------------------------------- 1 | class ChangeEnvelopeForeignKeysOnDelete < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_foreign_key :envelope_resources, :envelopes 4 | add_foreign_key :envelope_resources, :envelopes, on_delete: :cascade 5 | 6 | remove_foreign_key :envelope_transactions, :envelopes 7 | add_foreign_key :envelope_transactions, :envelopes, on_delete: :cascade 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20230126122421_include_envelope_community_id_into_description_set_index.rb: -------------------------------------------------------------------------------- 1 | class IncludeEnvelopeCommunityIdIntoDescriptionSetIndex < ActiveRecord::Migration[7.0] 2 | def change 3 | remove_index :description_sets, %i[ceterms_ctid path] 4 | 5 | add_index :description_sets, 6 | %i[ceterms_ctid envelope_community_id path], 7 | name: 'index_description_sets', 8 | unique: true 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20210715141032_create_publish_requests.rb: -------------------------------------------------------------------------------- 1 | class CreatePublishRequests < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :publish_requests do |t| 4 | t.text :request_params, null: false 5 | t.references :envelope, null: true, index: true, foreign_key: true 6 | t.jsonb :error, index: true 7 | t.datetime :completed_at, index: true 8 | t.timestamps null: false, index: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/redis-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: redis-config 5 | namespace: credreg-prod 6 | data: 7 | redis.conf: | 8 | bind 0.0.0.0 9 | port 6379 10 | requirepass your_secure_password 11 | appendonly yes 12 | maxmemory 500mb 13 | maxmemory-policy allkeys-lru 14 | tcp-keepalive 300 15 | protected-mode yes 16 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/clusterissuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-prod 5 | spec: 6 | acme: 7 | email: ariel@learningtapestry.com 8 | server: https://acme-v02.api.letsencrypt.org/directory 9 | privateKeySecretRef: 10 | name: letsencrypt-prod 11 | solvers: 12 | - dns01: 13 | route53: 14 | hostedZoneID: Z1N75467P1FUL5 15 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/clusterissuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-prod 5 | spec: 6 | acme: 7 | email: ariel@learningtapestry.com 8 | server: https://acme-v02.api.letsencrypt.org/directory 9 | privateKeySecretRef: 10 | name: letsencrypt-prod 11 | solvers: 12 | - dns01: 13 | route53: 14 | hostedZoneID: Z1N75467P1FUL5 15 | -------------------------------------------------------------------------------- /app/api/entities/json_context.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for JsonContext 4 | class JsonContext < Grape::Entity 5 | expose :context, 6 | documentation: { type: 'object', 7 | desc: 'The payload of the context' } 8 | expose :url, 9 | documentation: { type: 'string', 10 | desc: 'The URL of the context' } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/app-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: main-app-secrets 5 | type: Opaque 6 | stringData: 7 | POSTGRESQL_PASSWORD=[secret value] 8 | SECRET_KEY_BASE=[openssl rand -hex 32] 9 | POSTGRESQL_ADDRESS=[POSTGRESQL_ADDRESS] 10 | SIDEKIQ_USERNAME=[SIDEKIQ_USERNAME] 11 | SIDEKIQ_PASSWORD=[SIDEKIQ_PASSWORD] 12 | REDIS_URL=[REDIS_URL] 13 | AIRBRAKE_PROJECT_KEY=[AIRBRAKE_PROJECT_KEY] -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/app-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: main-app-secrets 5 | type: Opaque 6 | stringData: 7 | POSTGRESQL_PASSWORD=[secret value] 8 | SECRET_KEY_BASE=[openssl rand -hex 32] 9 | POSTGRESQL_ADDRESS=[POSTGRESQL_ADDRESS] 10 | SIDEKIQ_USERNAME=[SIDEKIQ_USERNAME] 11 | SIDEKIQ_PASSWORD=[SIDEKIQ_PASSWORD] 12 | REDIS_URL=[REDIS_URL] 13 | AIRBRAKE_PROJECT_KEY=[AIRBRAKE_PROJECT_KEY] -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'rake' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("rake", "rake") 18 | -------------------------------------------------------------------------------- /spec/factories/auth_tokens.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :auth_token do 3 | value { Faker::Lorem.characters(number: 32) } 4 | user 5 | 6 | trait :admin do 7 | # rubocop:todo FactoryBot/FactoryAssociationWithStrategy 8 | user { create(:user, :admin_account) } 9 | # rubocop:enable FactoryBot/FactoryAssociationWithStrategy 10 | end 11 | 12 | trait :publisher do # rubocop:todo Lint/EmptyBlock 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | brakeman: 4 | enabled: true 5 | bundler-audit: 6 | enabled: true 7 | duplication: 8 | enabled: true 9 | config: 10 | languages: 11 | - ruby 12 | fixme: 13 | enabled: true 14 | rubocop: 15 | enabled: true 16 | ratings: 17 | paths: 18 | - Gemfile.lock 19 | - "**.erb" 20 | - "**.rb" 21 | - "**.slim" 22 | exclude_paths: 23 | - config/ 24 | - db/ 25 | - spec/ 26 | - bin/ 27 | - public/ 28 | -------------------------------------------------------------------------------- /config/newrelic.yml: -------------------------------------------------------------------------------- 1 | common: &default_settings 2 | app_name: <%= ENV['NEW_RELIC_APP_NAME'] %> 3 | agent_enabled: <%= ENV['NEW_RELIC_AGENT_ENABLED'] %> 4 | license_key: <%= ENV['NEW_RELIC_LICENSE_KEY'] %> 5 | log_level: info 6 | 7 | development: 8 | <<: *default_settings 9 | 10 | production: 11 | <<: *default_settings 12 | 13 | sandbox: 14 | <<: *default_settings 15 | 16 | staging: 17 | <<: *default_settings 18 | 19 | test: 20 | <<: *default_settings 21 | -------------------------------------------------------------------------------- /lib/tasks/search.rake: -------------------------------------------------------------------------------- 1 | namespace :search do 2 | desc 'Reset Index' 3 | task reindex: :cer_environment do 4 | require 'services/extract_envelope_resources' 5 | 6 | pbar = ProgressBar.create title: 'Indexing', total: Envelope.count 7 | 8 | Envelope.find_in_batches do |group| 9 | group.each do |item| 10 | ExtractEnvelopeResources.call(envelope: item) 11 | pbar.increment 12 | end 13 | end 14 | pbar.finish 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.cloud66/papertrail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Download and install the remote_syslog2 binary from papertrail 4 | wget https://github.com/papertrail/remote_syslog2/releases/download/v0.19/remote_syslog_linux_amd64.tar.gz 5 | tar xzf ./remote_syslog*.tar.gz 6 | cd remote_syslog 7 | sudo cp ./remote_syslog /usr/local/bin 8 | 9 | # Set it up as a service (assumes init script has already been cp'd) 10 | sudo chmod +x /etc/init.d/remote_syslog 11 | sudo service remote_syslog start 12 | -------------------------------------------------------------------------------- /app/jobs/extract_envelope_resources_job.rb: -------------------------------------------------------------------------------- 1 | require 'extract_envelope_resources' 2 | require 'index_envelope_job' 3 | require 'precalculate_description_sets_job' 4 | 5 | # Runs the ExtractEnvelopeResources service in background 6 | class ExtractEnvelopeResourcesJob < ActiveJob::Base 7 | def perform(envelope_id) 8 | envelope = Envelope.find(envelope_id) 9 | ExtractEnvelopeResources.call(envelope:) 10 | IndexEnvelopeJob.perform_later(envelope_id) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /bin/rackup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'rackup' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("rack", "rackup") 18 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'rspec' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("rspec-core", "rspec") 18 | -------------------------------------------------------------------------------- /db/migrate/20210601020245_create_query_logs.rb: -------------------------------------------------------------------------------- 1 | class CreateQueryLogs < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :query_logs do |t| 4 | t.string :engine, index: true, null: false 5 | t.datetime :started_at, index: true, null: false 6 | t.datetime :completed_at, index: true 7 | t.jsonb :ctdl 8 | t.jsonb :result 9 | t.jsonb :query_logic 10 | t.text :query 11 | t.text :error 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry/alex.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA3jhfOIzRC/cPMWHyBr+SakxQ6z3LFrB0nirkVcVINw3HQhVawk01 3 | O11HFTjd0BRIO5hgBYRKIcvM1mH05TVK5y3r1DQ60TFgJpa+9eitdBGU+I/zBv2C 4 | 4c4awnmSowGuSz/HIHOo2GnKwYiQQUspn/29tRRa/+brfVSVtBmUvVIcUJoKSPS0 5 | ePNCzIYIImlHNvAyILeJaRFDwyl3ZGfzkVS68zWsDhshyxOss969YfrzQJ/nK4lW 6 | SxpSXBnytzBfrJvaBYJ52JSPTubLdpLxY9nYOT1vYxc3XjGYTfL4jJRJh1VOJPSh 7 | MI7eo0leZNEODE5hwFiRJNtEeMgHv2UnPwIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry/max.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAwbK3s6mWituhHCnThb1Wh5EMI7OaTrsCCYXv+8Sl+crpx5/otRop 3 | eSAJO43xMcQhH8UdyW3YiU16bZx+uJQeQGbvYCaqCQ60QrJTZLrsDBZLDx4WGL1U 4 | Tj1QJFfZJttrGiF6y2WOWFY8Je1cuyvnh9dGltrL44IzE/OFA3tuqQDTiKVAsxZK 5 | Cn+uDTxUyskRQLc1wAvNdHOKq1KdbMFTWlXrwWqIOeB12nDVy2u70X3zuUCl4Uw7 6 | 2jZdbC5jmadRljvqBGiWzCHSFTZbRjBc/s39AIHQLEkpUwZXRIPbz8HHzdNAGqVO 7 | utVjy/DtXXwmY2s6aqpBpso7i67Wrwq+AQIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yaml: -------------------------------------------------------------------------------- 1 | name: Dependency Review 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | pull-requests: write 9 | 10 | jobs: 11 | dependecy-review: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 15 | - uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 16 | with: 17 | comment-summary-in-pr: on-failure 18 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry/anderson.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA35JBqCEfCFMuplTm0NvQxnvwAzQHVEUD8yvn6u3uVkKuX9oOPh4r 3 | Kw9j1D7wNK/70oEsvnuBwNWHT7jXdd1bMDiN0d/TPLFllA2u8+Rr8enXU/1WpxH1 4 | yQxF7lcHyrl07YJ5B3V4PfgdTOR5vm8PB1UxiTNyrdmdeJ0POhphudXUIJF7HGog 5 | cO3T12fASzjvBod4GQmaMg6Ffm875rw7f5ASPrslbmuQfwDI3wvEQw/Br4Tw0ltV 6 | GCxbsjCLymnoHS3TNiK9h8v+nGWrz+kz15RMiMkiKNI3CWYph9SANlkHNYycWTP+ 7 | UNUbpT4mqbXSXJN05SdSAJuQotc0SN7/4QIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry/rsaksida.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAyrq8iwvUeESW9MK8Dw2zXIRXWOOhCTzxAgP8rg2XS0OXASNcuiJJ 3 | dIvb0nER5eo326DBTjrilZ7uzRj3KUGzRhWor3cN+SQFm18yxfAyyQwrftbGuDpL 4 | uwSigidp11V1OtixEDirb95aBpV9NpzdA5kqWrN4OkD5R7982HL84twe2VleuKMf 5 | WG6FWCme8pDIki37sphcx3xkUvJnALfPAFfHiiXB56hrH8UINunM/Gx3g1g4keGS 6 | 16a68d6QCPQs3+EgEtBgEm29iRLttPl0ZkutMtK84YC9AX8qeCNbgyiQjetVteUi 7 | 4DFjOOLQIP/tNQBLlYcg8eCBlWCi7RG6nwIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry_dev/alex.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA3jhfOIzRC/cPMWHyBr+SakxQ6z3LFrB0nirkVcVINw3HQhVawk01 3 | O11HFTjd0BRIO5hgBYRKIcvM1mH05TVK5y3r1DQ60TFgJpa+9eitdBGU+I/zBv2C 4 | 4c4awnmSowGuSz/HIHOo2GnKwYiQQUspn/29tRRa/+brfVSVtBmUvVIcUJoKSPS0 5 | ePNCzIYIImlHNvAyILeJaRFDwyl3ZGfzkVS68zWsDhshyxOss969YfrzQJ/nK4lW 6 | SxpSXBnytzBfrJvaBYJ52JSPTubLdpLxY9nYOT1vYxc3XjGYTfL4jJRJh1VOJPSh 7 | MI7eo0leZNEODE5hwFiRJNtEeMgHv2UnPwIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry_dev/max.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAwbK3s6mWituhHCnThb1Wh5EMI7OaTrsCCYXv+8Sl+crpx5/otRop 3 | eSAJO43xMcQhH8UdyW3YiU16bZx+uJQeQGbvYCaqCQ60QrJTZLrsDBZLDx4WGL1U 4 | Tj1QJFfZJttrGiF6y2WOWFY8Je1cuyvnh9dGltrL44IzE/OFA3tuqQDTiKVAsxZK 5 | Cn+uDTxUyskRQLc1wAvNdHOKq1KdbMFTWlXrwWqIOeB12nDVy2u70X3zuUCl4Uw7 6 | 2jZdbC5jmadRljvqBGiWzCHSFTZbRjBc/s39AIHQLEkpUwZXRIPbz8HHzdNAGqVO 7 | utVjy/DtXXwmY2s6aqpBpso7i67Wrwq+AQIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/learning_registry/alex.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA3jhfOIzRC/cPMWHyBr+SakxQ6z3LFrB0nirkVcVINw3HQhVawk01 3 | O11HFTjd0BRIO5hgBYRKIcvM1mH05TVK5y3r1DQ60TFgJpa+9eitdBGU+I/zBv2C 4 | 4c4awnmSowGuSz/HIHOo2GnKwYiQQUspn/29tRRa/+brfVSVtBmUvVIcUJoKSPS0 5 | ePNCzIYIImlHNvAyILeJaRFDwyl3ZGfzkVS68zWsDhshyxOss969YfrzQJ/nK4lW 6 | SxpSXBnytzBfrJvaBYJ52JSPTubLdpLxY9nYOT1vYxc3XjGYTfL4jJRJh1VOJPSh 7 | MI7eo0leZNEODE5hwFiRJNtEeMgHv2UnPwIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/learning_registry/max.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAwbK3s6mWituhHCnThb1Wh5EMI7OaTrsCCYXv+8Sl+crpx5/otRop 3 | eSAJO43xMcQhH8UdyW3YiU16bZx+uJQeQGbvYCaqCQ60QrJTZLrsDBZLDx4WGL1U 4 | Tj1QJFfZJttrGiF6y2WOWFY8Je1cuyvnh9dGltrL44IzE/OFA3tuqQDTiKVAsxZK 5 | Cn+uDTxUyskRQLc1wAvNdHOKq1KdbMFTWlXrwWqIOeB12nDVy2u70X3zuUCl4Uw7 6 | 2jZdbC5jmadRljvqBGiWzCHSFTZbRjBc/s39AIHQLEkpUwZXRIPbz8HHzdNAGqVO 7 | utVjy/DtXXwmY2s6aqpBpso7i67Wrwq+AQIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/external-secrets-operator.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: external-secrets.io/v1 3 | kind: ExternalSecret 4 | metadata: 5 | name: app-secret 6 | namespace: credreg-prod 7 | spec: 8 | refreshInterval: 1h 9 | secretStoreRef: 10 | name: aws-secret-manager 11 | kind: ClusterSecretStore 12 | target: 13 | name: app-secrets 14 | creationPolicy: Owner 15 | dataFrom: 16 | - extract: 17 | key: credreg-secrets-eks-production 18 | -------------------------------------------------------------------------------- /bin/overcommit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'overcommit' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("overcommit", "overcommit") 18 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry_dev/anderson.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA35JBqCEfCFMuplTm0NvQxnvwAzQHVEUD8yvn6u3uVkKuX9oOPh4r 3 | Kw9j1D7wNK/70oEsvnuBwNWHT7jXdd1bMDiN0d/TPLFllA2u8+Rr8enXU/1WpxH1 4 | yQxF7lcHyrl07YJ5B3V4PfgdTOR5vm8PB1UxiTNyrdmdeJ0POhphudXUIJF7HGog 5 | cO3T12fASzjvBod4GQmaMg6Ffm875rw7f5ASPrslbmuQfwDI3wvEQw/Br4Tw0ltV 6 | GCxbsjCLymnoHS3TNiK9h8v+nGWrz+kz15RMiMkiKNI3CWYph9SANlkHNYycWTP+ 7 | UNUbpT4mqbXSXJN05SdSAJuQotc0SN7/4QIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /config/authorized_keys/learning_registry/anderson.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEA35JBqCEfCFMuplTm0NvQxnvwAzQHVEUD8yvn6u3uVkKuX9oOPh4r 3 | Kw9j1D7wNK/70oEsvnuBwNWHT7jXdd1bMDiN0d/TPLFllA2u8+Rr8enXU/1WpxH1 4 | yQxF7lcHyrl07YJ5B3V4PfgdTOR5vm8PB1UxiTNyrdmdeJ0POhphudXUIJF7HGog 5 | cO3T12fASzjvBod4GQmaMg6Ffm875rw7f5ASPrslbmuQfwDI3wvEQw/Br4Tw0ltV 6 | GCxbsjCLymnoHS3TNiK9h8v+nGWrz+kz15RMiMkiKNI3CWYph9SANlkHNYycWTP+ 7 | UNUbpT4mqbXSXJN05SdSAJuQotc0SN7/4QIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /lib/tasks/json_contexts.rake: -------------------------------------------------------------------------------- 1 | require 'json_context' 2 | 3 | namespace :json_contexts do 4 | desc 'Updates the JSON context specs used for indexing envelope resources' 5 | task update: :cer_environment do 6 | urls = Envelope.distinct.pluck(Arel.sql("processed_resource->>'@context'")) 7 | 8 | urls.each do |url| 9 | next if url.blank? 10 | 11 | puts "Updating context for #{url}" 12 | JsonContext.update(url) 13 | puts 'Updated!' 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/models/auth_token_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe AuthToken do 2 | describe 'after_create' do 3 | it 'generates unique value' do 4 | existing_auth_token = create(:auth_token) 5 | value = Faker::Lorem.characters(number: 32) 6 | expect(SecureRandom).to receive(:hex) # rubocop:todo RSpec/MessageSpies 7 | .and_return(existing_auth_token.value, value) 8 | auth_token = described_class.create! 9 | expect(auth_token.value).to eq(value) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/envelope_version.rb: -------------------------------------------------------------------------------- 1 | # The custom subclass of PaperTrail::Version for envelopes 2 | class EnvelopeVersion < PaperTrail::Version 3 | enum :publication_status, MR.envelope_publication_statuses 4 | 5 | has_many :envelopes, primary_key: :item_id, foreign_key: :id 6 | 7 | scope :with_provisional_publication_status, lambda { |value| 8 | case value 9 | when 'only' 10 | provisional 11 | when 'include' 12 | all 13 | else 14 | full 15 | end 16 | } 17 | end 18 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/elasticsearch-headless-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: elasticsearch-discovery 5 | namespace: credreg-prod 6 | labels: 7 | app: elasticsearch 8 | spec: 9 | clusterIP: None 10 | publishNotReadyAddresses: true 11 | selector: 12 | app: elasticsearch 13 | ports: 14 | - name: http 15 | port: 9200 16 | targetPort: 9200 17 | - name: transport 18 | port: 9300 19 | targetPort: 9300 20 | -------------------------------------------------------------------------------- /db/migrate/20160407152817_create_versions.rb: -------------------------------------------------------------------------------- 1 | class CreateVersions < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :versions do |t| 4 | t.string :item_type, null: false 5 | t.integer :item_id, null: false 6 | t.string :event, null: false 7 | t.string :whodunnit 8 | t.jsonb :object, default: '{}' 9 | t.datetime :created_at 10 | end 11 | add_index :versions, [:item_type, :item_id] 12 | add_index :versions, :object, using: :gin 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/elasticsearch-headless-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: elasticsearch-discovery 5 | namespace: credreg-sandbox 6 | labels: 7 | app: elasticsearch 8 | spec: 9 | clusterIP: None 10 | publishNotReadyAddresses: true 11 | selector: 12 | app: elasticsearch 13 | ports: 14 | - name: http 15 | port: 9200 16 | targetPort: 9200 17 | - name: transport 18 | port: 9300 19 | targetPort: 9300 20 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/elasticsearch-headless-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: elasticsearch-discovery 5 | namespace: credreg-staging 6 | labels: 7 | app: elasticsearch 8 | spec: 9 | clusterIP: None 10 | publishNotReadyAddresses: true 11 | selector: 12 | app: elasticsearch 13 | ports: 14 | - name: http 15 | port: 9200 16 | targetPort: 9200 17 | - name: transport 18 | port: 9300 19 | targetPort: 9300 20 | -------------------------------------------------------------------------------- /spec/factories/envelope_resources.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :envelope_resource do 3 | envelope factory: %i[envelope] 4 | processed_resource { envelope.processed_resource } 5 | envelope_id { envelope.id } 6 | envelope_type { envelope.envelope_type } 7 | resource_id do 8 | processed_resource[envelope.envelope_community.id_field] || 9 | processed_resource.try(:[], '@id') || 10 | envelope.id 11 | end 12 | updated_at { envelope.updated_at } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20230703110903_create_envelope_downloads.rb: -------------------------------------------------------------------------------- 1 | class CreateEnvelopeDownloads < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :envelope_downloads, id: :uuid do |t| 4 | t.references :envelope_community, null: false 5 | t.datetime :finished_at 6 | t.string :internal_error_backtrace, array: true, default: [] 7 | t.string :internal_error_message 8 | t.datetime :started_at 9 | t.string :url 10 | 11 | t.timestamps null: false 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/envelope_download.rb: -------------------------------------------------------------------------------- 1 | # Stores the status and AWS S3 URL of an asynchronously performed envelope download 2 | class EnvelopeDownload < ActiveRecord::Base 3 | belongs_to :envelope_community 4 | has_many :envelopes, -> { not_deleted }, through: :envelope_community 5 | 6 | enum :status, { 7 | finished: 'finished', 8 | in_progress: 'in_progress', 9 | pending: 'pending' 10 | } 11 | 12 | def display_status 13 | return 'failed' if internal_error_message? 14 | 15 | status 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config/authorized_keys/learning_registry/mr_default.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyLYNBXiEcTF9OaSmmJ8r 3 | lpd1KEdufmvhpt8MlUTWnOEJr0CrWwvR/jMJ5B9CGMcu83Mcb214hcynoAxPrJJS 4 | L/pLUtY7xhYFILXDcXu/+Rl3I7km3mXzDc7uuD3DK84Ed70QsFkIR9BzX1VGwDQx 5 | JEKq4GNljXTV0QvAuiQiVFSFzPh4p9lDaUzGGhzDLiTNiS6Icq6bqc/mUNApRWNY 6 | lF13PDWksGGyUlhgFP3FFOPj2qYi4FDf8ToHYdOziFAYTtkSQjUvRhkz+xDVSR6p 7 | ow742ZZs078Ubyin01Qe9qTbZhby6wuXoIBHfch9/QvlGKLVxcd4utii1A8Q/IGl 8 | TwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /config/attribute_normalizers.rb: -------------------------------------------------------------------------------- 1 | AttributeNormalizer.configure do |config| 2 | config.normalizers[:downcase] = lambda do |value, _| 3 | value.is_a?(String) ? value.downcase : value 4 | end 5 | 6 | config.normalizers[:remove_spaces] = lambda do |value, _| 7 | value.is_a?(String) ? value.gsub(/[[:space:]]/, '') : value 8 | end 9 | 10 | config.normalizers[:underscore] = lambda do |value, _| 11 | value.is_a?(String) ? value.underscore : value 12 | end 13 | 14 | config.default_normalizers = :blank, :strip 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20171109230956_create_key_pairs.rb: -------------------------------------------------------------------------------- 1 | class CreateKeyPairs < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :key_pairs do |t| 4 | t.binary :encrypted_private_key, null: false 5 | t.binary :iv, null: false 6 | t.references :organization_publisher, foreign_key: true, null: false 7 | t.string :public_key, null: false 8 | t.integer :status, default: 1, null: false 9 | 10 | t.timestamps null: false 11 | 12 | t.index :public_key, unique: true 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20171104152617_create_organization_publishers.rb: -------------------------------------------------------------------------------- 1 | class CreateOrganizationPublishers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :organization_publishers do |t| 4 | t.uuid :organization_id, null: false 5 | t.uuid :publisher_id, null: false 6 | 7 | t.index %i[organization_id publisher_id], name: 'index_organization_publishers', unique: true 8 | 9 | t.foreign_key :organizations, on_delete: :cascade 10 | t.foreign_key :publishers, on_delete: :cascade 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /fixtures/schemas/json_schema.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "MetadataRegistry json-schema config", 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "description": "The resource schema name, for example: 'organization/resource_type'.", 8 | "type": "string" 9 | }, 10 | "schema": { 11 | "description": "The json-schema itself", 12 | "type": "object" 13 | } 14 | }, 15 | 16 | "required": [ 17 | "name", 18 | "schema" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /app/api/entities/envelope_community_config_version.rb: -------------------------------------------------------------------------------- 1 | require 'entities/envelope_community_config' 2 | 3 | module API 4 | module Entities 5 | # Presenter for the envelope community config 6 | class EnvelopeCommunityConfigVersion < Grape::Entity 7 | expose :id 8 | expose :created_at, as: :changed_at 9 | expose :object, as: :previous_version, using: EnvelopeCommunityConfig 10 | expose :diff 11 | 12 | def diff 13 | object.changeset.slice(:description, :payload) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20181121213645_change_envelope_resources_index.rb: -------------------------------------------------------------------------------- 1 | class ChangeEnvelopeResourcesIndex < ActiveRecord::Migration[4.2] 2 | def up 3 | remove_index :envelope_resources, :resource_id 4 | 5 | # No longer have an unique index 6 | add_index :envelope_resources, :resource_id 7 | end 8 | 9 | def down 10 | remove_index :envelope_resources, :resource_id 11 | 12 | execute 'delete from envelope_resources' 13 | # No longer have an unique index 14 | add_index :envelope_resources, :resource_id, unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # must be unique in a given SonarQube instance 2 | sonar.projectKey=CredentialEngine 3 | # defaults to project key 4 | sonar.projectName=Credential Engine Registry 5 | # Rubocop results report 6 | sonar.ruby.rubocop.reportPaths=rubocop-report.json 7 | # Test coverage report 8 | sonar.ruby.coverage.reportPaths=coverage/coverage.json 9 | sonar.tests = spec/ 10 | sonar.exclusions = docs/ 11 | sonar.exclusions=public/swagger/** 12 | sonar.language=ruby 13 | sonar.sourceEncoding=UTF-8 14 | sonar.sources=app,lib,config,public,Dockerfile 15 | -------------------------------------------------------------------------------- /app/api/base.rb: -------------------------------------------------------------------------------- 1 | require 'v1/base' 2 | require 'v2/base' 3 | 4 | module API 5 | # Main base class that defines all API versions 6 | class Base < Grape::API 7 | insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger, { 8 | logger: MR.logger, 9 | filter: Class.new do 10 | def filter(opts) 11 | opts['resource'] = '[FILTERED]' if opts['resource'] && MR.env == 'production' 12 | opts 13 | end 14 | end.new 15 | } 16 | 17 | mount API::V1::Base 18 | mount API::V2::Base 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /docs/samples/python/jwt_encode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import jwt 4 | from jwt.contrib.algorithms.pycrypto import RSAAlgorithm 5 | 6 | jwt.register_algorithm('RS256', RSAAlgorithm(RSAAlgorithm.SHA256)) 7 | 8 | def read_file(path): 9 | with open(path, 'r') as f: 10 | content = f.read() 11 | return content 12 | 13 | def jwt_encode(data, key_path): 14 | pkey = read_file(key_path) 15 | return jwt.encode(data, pkey, algorithm='RS256') 16 | 17 | data = json.loads(read_file(sys.argv[1])) 18 | key_path = sys.argv[2] 19 | 20 | print(jwt_encode(data, key_path)) 21 | -------------------------------------------------------------------------------- /spec/factories/registry_metadatas.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :registry_metadata do 3 | digital_signature { { key_location: ['http://example.org/pubkey'] } } 4 | terms_of_service do 5 | { submission_tos: 'http://example.org/tos' } 6 | end 7 | identity do 8 | { 9 | submitter: 'john doe ', 10 | signer: 'Alpha Node ', 11 | submitter_type: 'user' 12 | } 13 | end 14 | payload_placement { 'inline' } 15 | initialize_with { new(attributes) } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/factories/delete_tokens.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :delete_token do 3 | delete_token { jwt_encode({ delete: true }) } 4 | delete_token_format { :json } 5 | delete_token_encoding { :jwt } 6 | delete_token_public_key { Secrets.public_key } 7 | 8 | trait :with_malformed_key do 9 | delete_token_public_key { '----- MALFORMED PUBLIC KEY -----' } 10 | end 11 | 12 | trait :with_different_key do 13 | delete_token_public_key do 14 | OpenSSL::PKey::RSA.generate(2048).public_key.to_s 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /public/swagger/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "/swagger.json", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | ], 12 | plugins: [ 13 | SwaggerUIBundle.plugins.DownloadUrl 14 | ], 15 | layout: "StandaloneLayout" 16 | }); 17 | 18 | // 19 | }; 20 | -------------------------------------------------------------------------------- /spec/api/v1/home_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe API::V1::Home do 2 | context 'GET /' do # rubocop:todo RSpec/ContextWording 3 | before do 4 | get '/readme' 5 | end 6 | 7 | context 'valid schema' do # rubocop:todo RSpec/ContextWording 8 | it { expect_status(:ok) } 9 | 10 | it 'renders the erb template' do 11 | expect(response.body).to match(/\n.*/) 12 | end 13 | 14 | it 'renders the markdown' do 15 | expect(response.body).to match(/Credential Registry API/) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20181001205658_add_envelope_ceterms_ctid_and_envelope_ctdl_type_to_enveloeps.rb: -------------------------------------------------------------------------------- 1 | class AddEnvelopeCetermsCtidAndEnvelopeCtdlTypeToEnveloeps < ActiveRecord::Migration[4.2] 2 | def change 3 | change_table :envelopes do |t| 4 | t.string :envelope_ceterms_ctid, index: true 5 | t.string :envelope_ctdl_type, index: true 6 | end 7 | 8 | execute <<~SQL 9 | UPDATE 10 | envelopes 11 | SET 12 | envelope_ceterms_ctid = processed_resource->>'ceterms:ctid', 13 | envelope_ctdl_type = processed_resource->>'@type' 14 | SQL 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20171101211441_create_organizations.rb: -------------------------------------------------------------------------------- 1 | class CreateOrganizations < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :organizations, id: :uuid do |t| 4 | t.references :admin, foreign_key: true, null: false 5 | t.string :description 6 | t.string :name, null: false 7 | 8 | t.timestamps null: false 9 | end 10 | 11 | reversible do |dir| 12 | dir.up do 13 | connection.execute(%q{ 14 | CREATE UNIQUE INDEX index_organizations_on_name 15 | ON organizations(LOWER(name)) 16 | }) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/page.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | <%= title %> 15 | 16 | 17 |
<%= body %>
18 | 19 | 20 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/newrelic-apm-enable.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: newrelic.com/v1beta2 2 | kind: Instrumentation 3 | metadata: 4 | name: newrelic-instrumentation 5 | namespace: newrelic 6 | spec: 7 | agent: 8 | language: ruby 9 | image: newrelic/newrelic-ruby-init:latest 10 | 11 | namespaceLabelSelector: 12 | matchExpressions: 13 | - key: "kubernetes.io/metadata.name" 14 | operator: "In" 15 | values: ["credreg-prod"] 16 | 17 | podLabelSelector: 18 | matchExpressions: 19 | - key: "app-lang" 20 | operator: "In" 21 | values: ["ruby"] 22 | 23 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/newrelic-apm-enable.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: newrelic.com/v1beta2 2 | kind: Instrumentation 3 | metadata: 4 | name: newrelic-instrumentation 5 | namespace: newrelic 6 | spec: 7 | agent: 8 | language: ruby 9 | image: newrelic/newrelic-ruby-init:latest 10 | 11 | namespaceLabelSelector: 12 | matchExpressions: 13 | - key: "kubernetes.io/metadata.name" 14 | operator: "In" 15 | values: ["credreg-staging"] 16 | 17 | podLabelSelector: 18 | matchExpressions: 19 | - key: "app-lang" 20 | operator: "In" 21 | values: ["ruby"] 22 | -------------------------------------------------------------------------------- /docs/99_developer_notes.md: -------------------------------------------------------------------------------- 1 | # Developer Notes 2 | 3 | ## Helper scripts 4 | 5 | A few helper scripts to help development and testing are provided in the `bin/` 6 | folder: 7 | 8 | - `bin/update_json_schemas` updates the [JSON Schemas](/docs/08_schemas.md#schemas) with the latest version 9 | from http://credreg.net 10 | - `bin/create_resource` [creates a resource](/docs/02_ce-registry_walkthrough.md#1---resource) 11 | - `bin/update_resource` [updates a resource](/docs/02_ce-registry_walkthrough.md#7---updating-the-resource) 12 | - `bin/delete_resource` [deletes a resource](/docs/02_ce-registry_walkthrough.md#9---deleting-envelopes) 13 | -------------------------------------------------------------------------------- /fixtures/configs/learning_registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Config for LearningRegistry", 3 | 4 | "identifier": "url", 5 | 6 | "aliases": { 7 | "learning_resource_type": "learningResourceType" 8 | }, 9 | 10 | "prepared_queries": { 11 | "standard": "processed_resource @> '{ \"educationalAlignment\": [ {\"targetName\": \"$term\"} ] }'", 12 | "publisher_name": "processed_resource @> '{ \"publisher\": { \"name\": \"$term\" } }'" 13 | }, 14 | 15 | "fts": { 16 | "full": ["$.name", "$.keywords", "$.description"], 17 | "partial": ["$.name", "$.keywords"] 18 | }, 19 | "properties": { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/api/entities/envelope_event.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for an Envelope Version 4 | class EnvelopeEvent < Grape::Entity 5 | expose :envelope_ceterms_ctid, 6 | documentation: { type: 'string', 7 | desc: 'The CTID of the envelope' } 8 | expose :event, 9 | documentation: { type: 'string', 10 | desc: 'What change occurred' } 11 | expose :created_at, 12 | documentation: { type: 'datetime', 13 | desc: 'When the event was created' } 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/airbrake_load.rb: -------------------------------------------------------------------------------- 1 | project_id = ENV.fetch('AIRBRAKE_PROJECT_ID', nil) 2 | project_key = ENV.fetch('AIRBRAKE_PROJECT_KEY', nil) 3 | 4 | if project_id && project_key 5 | require 'airbrake' 6 | require 'airbrake/rack' 7 | 8 | if defined?(Sidekiq) 9 | Sidekiq.configure_server do |config| 10 | config.on(:startup) do 11 | require 'airbrake/sidekiq' 12 | end 13 | end 14 | end 15 | 16 | Airbrake.configure do |c| 17 | c.environment = ENV.fetch('RACK_ENV') 18 | c.project_id = project_id 19 | c.project_key = project_key 20 | c.blocklist_keys = %i[apikey api_key password] 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20171101194708_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :users do |t| 4 | t.references :admin, foreign_key: true 5 | t.string :email, null: false 6 | t.uuid :publisher_id 7 | 8 | t.timestamps null: false 9 | 10 | t.index :publisher_id 11 | 12 | t.foreign_key :publishers 13 | end 14 | 15 | reversible do |dir| 16 | dir.up do 17 | connection.execute(%q{ 18 | CREATE UNIQUE INDEX index_users_on_email 19 | ON users(LOWER(email)) 20 | }) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /terraform/modules/secrets/variables.tf: -------------------------------------------------------------------------------- 1 | variable "secret_name" { 2 | description = "Name of the Secrets Manager secret (must be unique per region)." 3 | type = string 4 | } 5 | 6 | variable "description" { 7 | description = "Description of the secret." 8 | type = string 9 | default = null 10 | } 11 | 12 | variable "secret_values" { 13 | description = "Map of key/value pairs that will be stored as JSON in the secret." 14 | type = map(string) 15 | } 16 | 17 | variable "tags" { 18 | description = "Optional tags to attach to the secret." 19 | type = map(string) 20 | default = {} 21 | } 22 | -------------------------------------------------------------------------------- /app/jobs/concerns/deduplicatable.rb: -------------------------------------------------------------------------------- 1 | # Prevents duplicated jobs from being enqueued 2 | module Deduplicatable 3 | extend ActiveSupport::Concern 4 | 5 | class_methods do 6 | def perform_later(*args) 7 | Sidekiq::Queue.new(queue_as).each do |job| 8 | metadata = job.args.first 9 | klass = metadata.fetch('job_class') 10 | params = metadata.fetch('arguments') 11 | 12 | # rubocop:todo Lint/NonLocalExitFromIterator 13 | return if klass == itself.to_s && params == args 14 | # rubocop:enable Lint/NonLocalExitFromIterator 15 | end 16 | 17 | super 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/jobs/download_envelopes_job.rb: -------------------------------------------------------------------------------- 1 | require 'download_envelopes' 2 | require 'envelope_download' 3 | 4 | # Create a ZIP archive contaning all of the envelopes from a certain community, 5 | # then uploads it to the S3 bucket 6 | class DownloadEnvelopesJob < ActiveJob::Base 7 | queue_as :envelope_download 8 | 9 | def perform(envelope_download_id) 10 | envelope_download = EnvelopeDownload.find_by(id: envelope_download_id) 11 | return unless envelope_download 12 | 13 | DownloadEnvelopes.call(envelope_download:) 14 | rescue StandardError => e 15 | Airbrake.notify(e, envelope_download_id:) 16 | raise e 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /terraform/environments/github-ci-oidc/trust.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Federated": "arn:aws:iam::996810415034:oidc-provider/token.actions.githubusercontent.com" 8 | }, 9 | "Action": "sts:AssumeRoleWithWebIdentity", 10 | "Condition": { 11 | "StringEquals": { 12 | "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" 13 | }, 14 | "StringLike": { 15 | "token.actions.githubusercontent.com:sub": "repo:[organization/repository]:*" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /app/api/entities/description_set_data.rb: -------------------------------------------------------------------------------- 1 | require 'entities/description_set_group' 2 | 3 | module API 4 | module Entities 5 | # Presenter for description set collections 6 | class DescriptionSetData < Grape::Entity 7 | expose :resources, as: :data 8 | 9 | expose :description_set_groups, 10 | as: :description_sets, 11 | using: DescriptionSetGroup 12 | 13 | expose :subresources, 14 | as: :description_set_resources, 15 | if: ->(object) { object.subresources } 16 | 17 | expose :results_metadata, if: ->(object) { object.results_metadata } 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/extensions/transactionable_envelope.rb: -------------------------------------------------------------------------------- 1 | require 'envelope_transaction' 2 | 3 | # Takes care of logging all the transactions of a given envelope 4 | module TransactionableEnvelope 5 | def self.included(base) 6 | base.class_eval do 7 | has_many :envelope_transactions, dependent: :delete_all 8 | 9 | after_create -> { log_operation(:created) } 10 | after_update -> { log_operation(:updated) } 11 | end 12 | end 13 | 14 | def log_operation(status) 15 | transaction_status = previous_changes.key?('deleted_at') ? :deleted : status 16 | envelope_transactions.create!(status: transaction_status) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/api/v1/indexer.rb: -------------------------------------------------------------------------------- 1 | require 'indexer_stats' 2 | require 'helpers/community_helpers' 3 | require 'helpers/envelope_helpers' 4 | 5 | module API 6 | module V1 7 | # Indexed resources API endpoints 8 | class Indexer < MountableAPI 9 | mounted do 10 | helpers CommunityHelpers 11 | helpers SharedHelpers 12 | 13 | before do 14 | authenticate! 15 | end 16 | 17 | namespace :indexer do 18 | desc 'Shows how many indexing jobs are in the queue' 19 | get 'stats' do 20 | IndexerStats.call(select_community) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/priorityclasses.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scheduling.k8s.io/v1 2 | kind: PriorityClass 3 | metadata: 4 | name: prod-high 5 | value: 1000 6 | globalDefault: false 7 | description: "High priority for production workloads" 8 | --- 9 | apiVersion: scheduling.k8s.io/v1 10 | kind: PriorityClass 11 | metadata: 12 | name: sandbox-medium 13 | value: 500 14 | globalDefault: false 15 | description: "Medium priority for sandbox workloads" 16 | --- 17 | apiVersion: scheduling.k8s.io/v1 18 | kind: PriorityClass 19 | metadata: 20 | name: sandbox-low 21 | value: 100 22 | globalDefault: false 23 | description: "Low priority for sandbox workloads" 24 | 25 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/priorityclasses.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scheduling.k8s.io/v1 2 | kind: PriorityClass 3 | metadata: 4 | name: prod-high 5 | value: 1000 6 | globalDefault: false 7 | description: "High priority for production workloads" 8 | --- 9 | apiVersion: scheduling.k8s.io/v1 10 | kind: PriorityClass 11 | metadata: 12 | name: staging-medium 13 | value: 500 14 | globalDefault: false 15 | description: "Medium priority for staging workloads" 16 | --- 17 | apiVersion: scheduling.k8s.io/v1 18 | kind: PriorityClass 19 | metadata: 20 | name: sandbox-low 21 | value: 100 22 | globalDefault: false 23 | description: "Low priority for sandbox workloads" 24 | 25 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/app-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: main-app-config 5 | namespace: credreg-prod 6 | data: 7 | POSTGRESQL_DATABASE: credential_registry_production 8 | POSTGRESQL_USERNAME: credential_registry_production 9 | RACK_ENV: production 10 | DOCKER_ENV: "true" 11 | ENVELOPE_GRAPHS_BUCKET: cer-envelope-graphs-prod 12 | ENVELOPE_DOWNLOADS_BUCKET: cer-envelope-downloads 13 | AIRBRAKE_PROJECT_ID: '270205' 14 | SIDEKIQ_CONCURRENCY: '10' 15 | API_KEY_VALIDATION_ENDPOINT: https://apps.credentialengine.org/accountsAPI/Organization/ValidateApiKey 16 | ELASTICSEARCH_ADDRESS: http://elasticsearch:9200 -------------------------------------------------------------------------------- /fixtures/schemas/publish_envelope.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "@id": { 6 | "type": "string" 7 | }, 8 | "@graph": { 9 | "type": "array", 10 | "items": { 11 | "type": "object", 12 | "properties": { 13 | "@id": { 14 | "type": "string" 15 | }, 16 | "@type": { 17 | "type": "string" 18 | } 19 | }, 20 | "required": ["@id", "@type"], 21 | "additionalProperties": true 22 | } 23 | } 24 | }, 25 | "required": ["@graph", "@id"], 26 | "additionalProperties": true 27 | } 28 | -------------------------------------------------------------------------------- /config/init_sidekiq.rb: -------------------------------------------------------------------------------- 1 | Sidekiq.configure_server do |config| 2 | config.capsule('description-set') do |capsule| 3 | capsule.concurrency = ( 4 | ENV['PRECALCULATE_DESCRIPTION_SETS_CONCURRENCY'].presence || 1 5 | ).to_i 6 | 7 | capsule.queues = %w[description_set] 8 | end 9 | 10 | config.capsule('envelope-download') do |capsule| 11 | capsule.concurrency = 1 12 | capsule.queues = %w[envelope_download] 13 | end 14 | 15 | config.redis = { db: ENV['REDIS_DB'].to_i } if MR.development? 16 | end 17 | 18 | Sidekiq.configure_client do |config| 19 | if MR.development? 20 | config.logger = nil 21 | config.redis = { db: ENV['REDIS_DB'].to_i } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20171101194114_create_publishers.rb: -------------------------------------------------------------------------------- 1 | class CreatePublishers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :publishers, id: :uuid do |t| 4 | t.references :admin, foreign_key: true, null: false 5 | t.string :contact_info 6 | t.string :description 7 | t.string :name, null: false 8 | t.boolean :super_publisher, default: false, null: false 9 | 10 | t.timestamps null: false 11 | end 12 | 13 | reversible do |dir| 14 | dir.up do 15 | connection.execute(%q{ 16 | CREATE UNIQUE INDEX index_publishers_on_name 17 | ON publishers(LOWER(name)) 18 | }) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: registry-certificate-temp 5 | namespace: credreg-prod 6 | spec: 7 | secretName: registry-tls-temp 8 | issuerRef: 9 | name: letsencrypt-prod 10 | kind: ClusterIssuer 11 | dnsNames: 12 | - registry-prod.credentialengineregistry.org 13 | 14 | --- 15 | apiVersion: cert-manager.io/v1 16 | kind: Certificate 17 | metadata: 18 | name: registry-certificate 19 | namespace: credreg-prod 20 | spec: 21 | secretName: registry-tls 22 | issuerRef: 23 | name: letsencrypt-prod 24 | kind: ClusterIssuer 25 | dnsNames: 26 | - credentialengineregistry.org -------------------------------------------------------------------------------- /app/api/v1/ce_registry.rb: -------------------------------------------------------------------------------- 1 | require 'helpers/shared_helpers' 2 | 3 | module API 4 | module V1 5 | # CE/Registry-specific api endpoints 6 | class CERegistry < Grape::API 7 | helpers SharedHelpers 8 | 9 | route_param :envelope_community do 10 | before_validation do 11 | unless params[:envelope_community].underscore.include?('ce_registry') 12 | msg = 'envelope_community does not have a valid value' 13 | error!({ errors: msg }, 400) 14 | end 15 | end 16 | 17 | desc 'Generate a CE/Registry CTID' 18 | get 'ctid' do 19 | { ctid: Envelope.generate_ctid } 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/app-hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: main-app-hpa 5 | namespace: credreg-sandbox 6 | labels: 7 | app: laravel 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: main-app 13 | minReplicas: 1 14 | maxReplicas: 5 15 | metrics: 16 | - type: Resource 17 | resource: 18 | name: cpu 19 | target: 20 | type: Utilization 21 | averageUtilization: 60 22 | - type: Resource 23 | resource: 24 | name: memory 25 | target: 26 | type: Utilization 27 | averageUtilization: 70 28 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/app-hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: main-app-hpa 5 | namespace: credreg-staging 6 | labels: 7 | app: laravel 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: main-app 13 | minReplicas: 1 14 | maxReplicas: 5 15 | metrics: 16 | - type: Resource 17 | resource: 18 | name: cpu 19 | target: 20 | type: Utilization 21 | averageUtilization: 60 22 | - type: Resource 23 | resource: 24 | name: memory 25 | target: 26 | type: Utilization 27 | averageUtilization: 70 28 | -------------------------------------------------------------------------------- /app/models/query_log.rb: -------------------------------------------------------------------------------- 1 | # Tracks execution for a query sent by a client 2 | class QueryLog < ActiveRecord::Base 3 | def self.start(params = {}) 4 | query_log = new(params.to_h.merge(started_at: Time.now)) 5 | query_log.save 6 | query_log 7 | end 8 | 9 | def complete(result) 10 | update(result: result, completed_at: Time.now) 11 | end 12 | 13 | def fail(error) 14 | update(completed_at: Time.now, error: error) 15 | end 16 | 17 | def ctdl=(value) 18 | super(value.present? ? value.to_json : nil) 19 | end 20 | 21 | def result=(value) 22 | super(value.present? ? value.to_json : nil) 23 | end 24 | 25 | def query_logic=(value) 26 | super(value.present? ? value.to_json : nil) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/models/authorized_key.rb: -------------------------------------------------------------------------------- 1 | # Check if public is authorized. Used for json_schema envelopes 2 | class AuthorizedKey 3 | attr_reader :community_name, :key 4 | 5 | def initialize(community_name, key) 6 | @community_name = community_name 7 | @key = key.gsub(/\n$/, '') # remove trailing newline 8 | end 9 | 10 | def valid? 11 | authorized_keys.include?(key) 12 | end 13 | 14 | def authorized_keys 15 | pattern = self.class.base_path + "/#{community_name}/*" 16 | Dir[pattern].reject { |file| file.start_with?('.') } 17 | .map { |path| File.read(path).gsub(/\n$/, '') } 18 | end 19 | 20 | def self.base_path 21 | @base_path ||= File.expand_path('../../config/authorized_keys', __dir__) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | EnvelopeCommunity.find_or_create_by(name: 'learning_registry', 2 | backup_item: 'learning-registry-test') 3 | 4 | EnvelopeCommunity.find_or_create_by(name: 'ce_registry', 5 | backup_item: 'ce-registry-test', 6 | default: true) 7 | 8 | admin = Admin.find_or_create_by(name: 'ce_admin') 9 | organization = Organization.find_or_create_by(name: 'Generic Organization', admin: admin) 10 | publisher = Publisher.find_or_create_by(name: 'ce', admin: admin, super_publisher: true) 11 | OrganizationPublisher.find_or_create_by(organization: organization, publisher: publisher) 12 | User.find_or_create_by(email: 'user@ce.org', publisher: publisher, admin: admin) 13 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/newrelic-apm-enable.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: newrelic.com/v1beta2 3 | kind: Instrumentation 4 | metadata: 5 | name: newrelic-instrumentation 6 | namespace: newrelic 7 | spec: 8 | agent: 9 | language: ruby 10 | 11 | image: newrelic/newrelic-ruby-init:latest 12 | 13 | # Select a namespace with a specific name by using "kubernetes.io/metadata.name" label 14 | namespaceLabelSelector: 15 | matchExpressions: 16 | - key: "kubernetes.io/metadata.name" 17 | operator: "In" 18 | values: ["credreg-sandbox"] 19 | 20 | # Narrow to pods labeled as Ruby apps 21 | podLabelSelector: 22 | matchExpressions: 23 | - key: "app-lang" 24 | operator: "In" 25 | values: ["ruby"] 26 | -------------------------------------------------------------------------------- /app/api/entities/organization.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for organizations 4 | class Organization < Grape::Entity 5 | expose :id, 6 | documentation: { type: 'string', 7 | desc: 'Unique identifier (in UUID format)' } 8 | expose :name, 9 | documentation: { type: 'string', 10 | desc: 'Name of this organization' } 11 | 12 | expose :_ctid, 13 | documentation: { type: 'string', 14 | desc: 'The Organization\'s CTID' } 15 | 16 | expose :description, 17 | documentation: { type: 'string', 18 | desc: 'Description of this organization' } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/api/v2/base.rb: -------------------------------------------------------------------------------- 1 | require 'defaults' 2 | require 'helpers/shared_helpers' 3 | require 'v2/publish' 4 | require 'v2/publish_requests' 5 | 6 | module API 7 | module V2 8 | # Base class that gathers all the API endpoints 9 | class Base < Grape::API 10 | include API::Defaults 11 | include Grape::Kaminari 12 | 13 | helpers SharedHelpers 14 | helpers Pundit::Authorization 15 | 16 | version 'v2', using: :accept_version_header 17 | 18 | desc 'used only for testing' 19 | get(:_test) { test_response } 20 | 21 | mount API::V2::Publish.api_class 22 | mount API::V2::PublishRequests.api_class 23 | 24 | route_param :community_name do 25 | mount API::V2::Publish.api_class 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/jobs/index_envelope_job.rb: -------------------------------------------------------------------------------- 1 | require 'index_envelope_resource' 2 | require 'precalculate_description_sets_job' 3 | 4 | # 1. Indexes each of the envelope's resources 5 | # 2. Precalculate description sets for the envelope 6 | class IndexEnvelopeJob < ActiveJob::Base 7 | def perform(envelope_id) 8 | envelope = Envelope.find_by(id: envelope_id) 9 | return unless envelope 10 | 11 | Parallel.each(envelope.envelope_resources.pluck(:id)) do |resource_id| 12 | resource = EnvelopeResource.find(resource_id) 13 | IndexEnvelopeResource.call(resource) 14 | rescue ActiveRecord::RecordNotUnique => e 15 | Airbrake.notify(e, resource_id: resource.resource_id) 16 | end 17 | 18 | PrecalculateDescriptionSetsJob.perform_later(envelope.id) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/api/entities/publisher.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for publishers 4 | class Publisher < Grape::Entity 5 | expose :id, 6 | documentation: { type: 'string', 7 | desc: 'Unique identifier (in UUID format)' } 8 | expose :name, 9 | documentation: { type: 'string', 10 | desc: 'Name of this publisher' } 11 | 12 | expose :description, 13 | documentation: { type: 'string', 14 | desc: 'Description of this publisher' } 15 | 16 | expose :contact_info, 17 | documentation: { type: 'string', 18 | desc: 'Contact information of this publisher' } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/api_user.rb: -------------------------------------------------------------------------------- 1 | # Represents a user with their community and roles 2 | class ApiUser 3 | ADMIN = 'admin'.freeze 4 | PUBLISHER = 'publisher'.freeze 5 | READER = 'reader'.freeze 6 | SUPERADMIN = 'superadmin'.freeze 7 | 8 | attr_reader :community, :roles, :user 9 | 10 | delegate :admin, :publisher, to: :user 11 | 12 | def initialize(community:, roles:, user:) 13 | @community = community 14 | @roles = Array.wrap(roles) 15 | @user = user 16 | end 17 | 18 | def admin? 19 | superadmin? || roles.include?(ADMIN) 20 | end 21 | 22 | def publisher? 23 | admin? || roles.include?(PUBLISHER) 24 | end 25 | 26 | def reader? 27 | publisher? || roles.include?(READER) 28 | end 29 | 30 | def superadmin? 31 | roles.include?(SUPERADMIN) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | AWS_REGION=us-east-2 2 | 3 | ENVELOPE_DOWNLOADS_BUCKET=envelope-downloads 4 | 5 | POSTGRESQL_ADDRESS=localhost 6 | POSTGRESQL_USERNAME=metadataregistry 7 | POSTGRESQL_PASSWORD=metadataregistry 8 | POSTGRESQL_DATABASE=metadataregistry_development 9 | REDIS_URL=redis://localhost:6379/0 10 | REDIS_POOL_SIZE=5 11 | 12 | INTERNET_ARCHIVE_ACCESS_KEY= 13 | INTERNET_ARCHIVE_SECRET_KEY= 14 | 15 | PRECALCULATE_DESCRIPTION_SETS_CONCURRENCY=1 16 | 17 | NEW_RELIC_AGENT_ENABLED=true 18 | NEW_RELIC_APP_NAME= 19 | NEW_RELIC_LICENSE_KEY= 20 | 21 | STATEMENT_TIMEOUT=300000 22 | 23 | IAM_CLIENT_ID= 24 | IAM_COMMUNITY_CLAIM_NAME= 25 | IAM_COMMUNITY_ROLE_ADMIN= 26 | IAM_COMMUNITY_ROLE_PUBLISHER= 27 | IAM_COMMUNITY_ROLE_READER= 28 | IAM_URL= 29 | SECRET_KEY_BASE=[secret key string for signed cookies] 30 | -------------------------------------------------------------------------------- /spec/services/purge_envelopes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'purge_envelopes' 2 | 3 | RSpec.describe PurgeEnvelopes do 4 | let!(:envelope1) { create(:envelope, purged_at: Time.current) } # rubocop:todo RSpec/IndexedLet 5 | let!(:envelope2) { create(:envelope, purged_at: Time.current) } # rubocop:todo RSpec/IndexedLet 6 | let!(:envelope3) { create(:envelope) } # rubocop:todo RSpec/IndexedLet 7 | 8 | describe '.call' do 9 | it 'deletes purged envelopes' do # rubocop:todo RSpec/MultipleExpectations 10 | expect { described_class.call }.to change(Envelope, :count).by(-2) 11 | expect { envelope1.reload }.to raise_error(ActiveRecord::RecordNotFound) 12 | expect { envelope2.reload }.to raise_error(ActiveRecord::RecordNotFound) 13 | expect { envelope3.reload }.not_to raise_error 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/img-inspect.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: img-inspect 5 | namespace: credreg-staging 6 | labels: 7 | app: img-inspect 8 | spec: 9 | restartPolicy: Never 10 | serviceAccountName: main-app-service-account 11 | containers: 12 | - name: inspector 13 | image: 996810415034.dkr.ecr.us-east-1.amazonaws.com/registry:staging 14 | imagePullPolicy: Always 15 | command: ["/bin/bash","-lc","sleep infinity"] 16 | envFrom: 17 | - secretRef: 18 | name: app-secrets 19 | - configMapRef: 20 | name: main-app-config 21 | resources: 22 | requests: 23 | cpu: "100m" 24 | memory: "128Mi" 25 | limits: 26 | cpu: "500m" 27 | memory: "512Mi" 28 | -------------------------------------------------------------------------------- /config/ar_tasks.rb: -------------------------------------------------------------------------------- 1 | include ActiveRecord::Tasks # rubocop:todo Style/MixinUsage 2 | 3 | class Seeder # rubocop:todo Style/Documentation 4 | attr_reader :seed_file 5 | 6 | def initialize(seed_file) 7 | @seed_file = seed_file 8 | end 9 | 10 | def load_seed 11 | raise "Seed file `#{seed_file}` doesn't exist" unless File.file?(seed_file) 12 | 13 | load(seed_file) 14 | end 15 | end 16 | 17 | DatabaseTasks.db_dir = MR.root_path.join('db') 18 | DatabaseTasks.env = ENV.fetch('RACK_ENV', nil) 19 | DatabaseTasks.migrations_paths = [MR.root_path.join('db/migrate')] 20 | DatabaseTasks.root = MR.root_path 21 | DatabaseTasks.seed_loader = Seeder.new(MR.root_path.join('db/seeds.rb')) 22 | 23 | load 'active_record/railties/databases.rake' 24 | 25 | task :environment do 26 | Rake::Task['cer_environment'].execute 27 | end 28 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/app-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: main-app 5 | namespace: credreg-prod 6 | spec: 7 | ingressClassName: nginx 8 | tls: 9 | - hosts: 10 | - registry-prod.credentialengineregistry.org 11 | secretName: registry-tls-temp 12 | # tls: 13 | # - hosts: 14 | # - credentialengineregistry.org 15 | # secretName: registry-tls 16 | rules: 17 | - host: registry-prod.credentialengineregistry.org 18 | # - host: credentialengineregistry.org 19 | http: 20 | paths: 21 | - path: / 22 | pathType: Prefix 23 | backend: 24 | service: 25 | name: main-app 26 | port: 27 | number: 9292 28 | 29 | -------------------------------------------------------------------------------- /app/models/query_condition.rb: -------------------------------------------------------------------------------- 1 | require 'dry-struct' 2 | 3 | Dry::Types.load_extensions(:maybe) 4 | 5 | # Convenience module for dry-types 6 | module Types 7 | include Dry::Types.module 8 | end 9 | 10 | # Placeholder class that represents a query condition in a graph search. 11 | class QueryCondition < Dry::Struct 12 | constructor_type :schema 13 | 14 | Operators = Types::String.default('EQUAL').enum('EQUAL', 'NOT_EQUAL', 'GREATER_THAN', 15 | 'LESS_THAN', 'CONTAINS', 'STARTS_WITH', 16 | 'ENDS_WITH') 17 | attribute :object, Types::Strict::String.maybe 18 | attribute :element, Types::Strict::String 19 | attribute :operator, Operators 20 | attribute :value, Types::Any 21 | attribute :optional, Types::Strict::Bool.default(false) 22 | end 23 | -------------------------------------------------------------------------------- /app/api/v1/users.rb: -------------------------------------------------------------------------------- 1 | require 'entities/user' 2 | require 'helpers/shared_helpers' 3 | require 'policies/user_policy' 4 | require 'user' 5 | 6 | module API 7 | module V1 8 | # User API endpoint 9 | class Users < Grape::API 10 | helpers CommunityHelpers 11 | helpers SharedHelpers 12 | 13 | resources :users do 14 | desc 'Creates a new user for a given publisher' 15 | before do 16 | authenticate! 17 | end 18 | params do 19 | requires :email, type: String, desc: 'User email' 20 | end 21 | post do 22 | authorize User, :create? 23 | current_user.admin.publishers.find(params[:publisher_id]) 24 | user = User.create!(params) 25 | present user, with: API::Entities::User 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /terraform/modules/secrets/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_secretsmanager_secret" "this" { 2 | name = var.secret_name 3 | description = var.description 4 | tags = var.tags 5 | } 6 | 7 | # Store the JSON-encoded map as the latest version of the secret. 8 | resource "aws_secretsmanager_secret_version" "this" { 9 | secret_id = aws_secretsmanager_secret.this.id 10 | secret_string = jsonencode(var.secret_values) 11 | 12 | lifecycle { 13 | # After the initial creation we usually want to manage the secret values 14 | # outside of Terraform (for example via AWS Console or CI pipelines). By 15 | # ignoring changes to `secret_string` we avoid drift whenever the local 16 | # variables contain placeholder or outdated values that differ from the 17 | # real secret in AWS. 18 | ignore_changes = [secret_string] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry/cer_key1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIICCgKCAgEAuvC6aDDGq4eKc0zgagoqXCzWwSHtczP4FAHGyQIGArZJdrwFfUg5 3 | KIaL0uQjjkbA5SanoP/w5xJhA8ilA2TjIPTc1Kpkjq2RBrALOPtsRVc9pbWjg1jb 4 | Kgg4KBi0MM965K3njeKiVy1REQ9i2z1xik40c2TXBoppXHoRfpznHV+BrIVO/85z 5 | HPov8GBLlmfZmrz2CqiI32aGHMqFEEfGcuRvq01KEbZUz5iTtx/TRLPpM6Akefa6 6 | DYlBlxcd9vEzJE8yQVg6RAPJiRcQG1xUDYqkF1Ys1XCFfZGT97bNHYLrtW+OW2EP 7 | LvMtPlEUtoVVy54z+WnFFi6LHOugHSAJkWnPM5LqfmAahkD1kJ6Pcib3vqzsdw1/ 8 | hJxB++AlBS8MTVdbYme7XT/BgzTLg1Vyy9szsqSix2faZPWQxMN2px7YkRQx9MUs 9 | GO6BGJ8Pg3DHUspLYubEBZSSggw7l0Yb96s3SNacbSlF8bvHfeSbw/UenNEAeOcG 10 | 8gi7H50rYpJrTnaCF9mtfRnnjSC8eH21DteJKyyXarWaXIWaAwQ7QpIpFGOdjKrf 11 | EGuMcmLCpAd8Ci2f6RKqQVpFw1irzietPHcp02d36P7Vx/zNYwjqZMhyO2JnILr+ 12 | pHpoPD3Ju2pCBFszEcXAzHnTUwJpATl6rjOxm4qNKAAkF3Zua4zHNsECAwEAAQ== 13 | -----END RSA PUBLIC KEY----- 14 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry/cer_key2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIICCgKCAgEA5KZSwNcmYO26sxIoD47r2/1NRV+GtcetmzicXG77omUww9zFTgbv 3 | au0VF/ttRu+1AnlluchXPSSPIwYebTyPYZ0HJttN0ZFlJU4oE9Arr9yDF+Z6ssMK 4 | pZLdDsew5B3nQ8YsGfRRF/KergevlayOD7rojU2awVcOhvfL90SFJTRbpbJNZjV3 5 | 9i1Lxy6B+zOUdVpwBLcWk5dH4rigqTSLixujCSULn8L4Ot52R+DUR2HwPzz/KjP1 6 | AkNA0+FE9RmjH7+4fyX5w3zpG7Umiqdq/30Hr/Z6tkmXl68vukS1pEm1ju6d1U7V 7 | T7zAy5tRFG7pXHJRfvGYo3yo2GvkwRi9Q+kspU4XitJrSUWZ8M8sDV6jm7ivjERs 8 | CdLiuTjDMKw564dk1Y9olFjR6tObbbdlBb+2nqV2ba2Q1GY53koVtC8uJwl3r8S7 9 | qfrQO+7iRwCjXCwyJzNQcfaDX+ySLvK53CeDimcMhUSGZd11Ei16n5FKJcN6qb4D 10 | m4/yHe5TS1ODVTEkYKs0VjYSEFokEAkEdPymf3F8ejqRL3wP8PYT31WQNu+BnbAn 11 | m46EYWwVCfAFyFySnXo/23AlItIKGA423D9pA12yh6A822gLtwusYKzBYy1n4IjA 12 | K4B7GS+7RQ5CYRuvwDDDa4/HjTk+4ohv9A0sFxdqWR+PZswRDEkyZv0CAwEAAQ== 13 | -----END RSA PUBLIC KEY----- 14 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry_dev/cer_key1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIICCgKCAgEAuvC6aDDGq4eKc0zgagoqXCzWwSHtczP4FAHGyQIGArZJdrwFfUg5 3 | KIaL0uQjjkbA5SanoP/w5xJhA8ilA2TjIPTc1Kpkjq2RBrALOPtsRVc9pbWjg1jb 4 | Kgg4KBi0MM965K3njeKiVy1REQ9i2z1xik40c2TXBoppXHoRfpznHV+BrIVO/85z 5 | HPov8GBLlmfZmrz2CqiI32aGHMqFEEfGcuRvq01KEbZUz5iTtx/TRLPpM6Akefa6 6 | DYlBlxcd9vEzJE8yQVg6RAPJiRcQG1xUDYqkF1Ys1XCFfZGT97bNHYLrtW+OW2EP 7 | LvMtPlEUtoVVy54z+WnFFi6LHOugHSAJkWnPM5LqfmAahkD1kJ6Pcib3vqzsdw1/ 8 | hJxB++AlBS8MTVdbYme7XT/BgzTLg1Vyy9szsqSix2faZPWQxMN2px7YkRQx9MUs 9 | GO6BGJ8Pg3DHUspLYubEBZSSggw7l0Yb96s3SNacbSlF8bvHfeSbw/UenNEAeOcG 10 | 8gi7H50rYpJrTnaCF9mtfRnnjSC8eH21DteJKyyXarWaXIWaAwQ7QpIpFGOdjKrf 11 | EGuMcmLCpAd8Ci2f6RKqQVpFw1irzietPHcp02d36P7Vx/zNYwjqZMhyO2JnILr+ 12 | pHpoPD3Ju2pCBFszEcXAzHnTUwJpATl6rjOxm4qNKAAkF3Zua4zHNsECAwEAAQ== 13 | -----END RSA PUBLIC KEY----- 14 | -------------------------------------------------------------------------------- /config/authorized_keys/ce_registry_dev/cer_key2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIICCgKCAgEA5KZSwNcmYO26sxIoD47r2/1NRV+GtcetmzicXG77omUww9zFTgbv 3 | au0VF/ttRu+1AnlluchXPSSPIwYebTyPYZ0HJttN0ZFlJU4oE9Arr9yDF+Z6ssMK 4 | pZLdDsew5B3nQ8YsGfRRF/KergevlayOD7rojU2awVcOhvfL90SFJTRbpbJNZjV3 5 | 9i1Lxy6B+zOUdVpwBLcWk5dH4rigqTSLixujCSULn8L4Ot52R+DUR2HwPzz/KjP1 6 | AkNA0+FE9RmjH7+4fyX5w3zpG7Umiqdq/30Hr/Z6tkmXl68vukS1pEm1ju6d1U7V 7 | T7zAy5tRFG7pXHJRfvGYo3yo2GvkwRi9Q+kspU4XitJrSUWZ8M8sDV6jm7ivjERs 8 | CdLiuTjDMKw564dk1Y9olFjR6tObbbdlBb+2nqV2ba2Q1GY53koVtC8uJwl3r8S7 9 | qfrQO+7iRwCjXCwyJzNQcfaDX+ySLvK53CeDimcMhUSGZd11Ei16n5FKJcN6qb4D 10 | m4/yHe5TS1ODVTEkYKs0VjYSEFokEAkEdPymf3F8ejqRL3wP8PYT31WQNu+BnbAn 11 | m46EYWwVCfAFyFySnXo/23AlItIKGA423D9pA12yh6A822gLtwusYKzBYy1n4IjA 12 | K4B7GS+7RQ5CYRuvwDDDa4/HjTk+4ohv9A0sFxdqWR+PZswRDEkyZv0CAwEAAQ== 13 | -----END RSA PUBLIC KEY----- 14 | -------------------------------------------------------------------------------- /db/migrate/20160825034410_add_tsvector_to_envelopes.rb: -------------------------------------------------------------------------------- 1 | class AddTsvectorToEnvelopes < ActiveRecord::Migration[4.2] 2 | def up 3 | add_column :envelopes, :fts_tsearch_tsv, :tsvector 4 | add_index :envelopes, :fts_tsearch_tsv, using: "gin" 5 | 6 | execute <<-SQL 7 | CREATE TRIGGER fts_tsvector_update BEFORE INSERT OR UPDATE 8 | ON envelopes FOR EACH ROW EXECUTE PROCEDURE 9 | tsvector_update_trigger(fts_tsearch_tsv, 'pg_catalog.simple', fts_tsearch); 10 | SQL 11 | 12 | # now = Time.current.to_s(:db) 13 | # update("UPDATE envelopes SET updated_at = '#{now}'") 14 | end 15 | 16 | def down 17 | execute <<-SQL 18 | DROP TRIGGER fts_tsvector_update 19 | ON envelopes; 20 | SQL 21 | 22 | remove_index :envelopes, :fts_tsearch_tsv 23 | remove_column :envelopes, :fts_tsearch_tsv 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/extensions/ce_registry_resources.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | 3 | # CE/Registry specific behavior for resource envelopes 4 | module CERegistryResources 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | validate :unique_ctid, if: :ce_registry?, unless: :deleted? 9 | 10 | def unique_ctid 11 | if Envelope.not_deleted 12 | .in_community('ce_registry') 13 | .where.not(envelope_id: envelope_id) 14 | .where(envelope_ceterms_ctid: processed_resource_ctid) 15 | .exists? 16 | errors.add :resource, 'CTID must be unique' 17 | end 18 | end 19 | 20 | def ce_registry? 21 | envelope_community.name.include?('ce_registry') 22 | end 23 | 24 | def self.generate_ctid 25 | "ce-#{SecureRandom.uuid}" 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("rubocop", "rubocop") 28 | -------------------------------------------------------------------------------- /spec/api/v1/ce_registry_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'CE/Registry API' do # rubocop:todo RSpec/DescribeClass 2 | describe 'GET /:community/ctid' do 3 | context 'ce_registry' do # rubocop:todo RSpec/ContextWording 4 | before { get '/ce-registry/ctid' } 5 | 6 | it { expect_status(:ok) } 7 | 8 | it { 9 | # rubocop:todo Layout/LineLength 10 | expect(json_resp['ctid']).to match(/^ce-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) 11 | # rubocop:enable Layout/LineLength 12 | } 13 | end 14 | 15 | context 'Other communities' do # rubocop:todo RSpec/ContextWording 16 | before { get '/learning-registry/ctid' } 17 | 18 | let(:err) { 'envelope_community does not have a valid value' } 19 | 20 | it { expect_status(400) } 21 | it { expect_json('errors', err) } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/json_context.rb: -------------------------------------------------------------------------------- 1 | # A JSON context payload from CredReg. 2 | class JsonContext < ActiveRecord::Base 3 | has_paper_trail 4 | 5 | validates :url, :context, presence: true 6 | validates :url, uniqueness: true 7 | 8 | class << self 9 | def context 10 | { 11 | 'ceterms:ctid' => { '@type' => 'xsd:string' }, 12 | 'search:recordCreated' => { '@type' => 'xsd:dateTime' }, 13 | 'search:recordUpdated' => { '@type' => 'xsd:dateTime' } 14 | }.merge( 15 | distinct 16 | .pluck(:context) 17 | .map { |c| c.fetch('@context') } 18 | .inject(&:merge) || {} 19 | ) 20 | end 21 | 22 | def update(url) 23 | context = JSON.parse(RestClient.get(url).body) 24 | json_context = JsonContext.find_or_initialize_by(url:) 25 | json_context.update!(context:) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20160223171632_create_envelopes.rb: -------------------------------------------------------------------------------- 1 | class CreateEnvelopes < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :envelopes do |t| 4 | t.integer :envelope_type, null: false, default: 0 5 | t.string :envelope_version, null: false, index: true 6 | t.string :envelope_id, null: false, index: { unique: true } 7 | t.text :resource, null: false 8 | t.integer :resource_format, null: false, default: 0 9 | t.integer :resource_encoding, null: false, default: 0 10 | t.text :resource_public_key, null: false 11 | t.text :node_headers 12 | t.integer :node_headers_format, default: 0 13 | t.jsonb :processed_resource, null: false, default: '{}' 14 | t.datetime :deleted_at 15 | 16 | t.timestamps null: false 17 | end 18 | add_index :envelopes, :processed_resource, using: :gin 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/api/v1/shared_examples/auth.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'requires auth' do |verb, path| 2 | context 'no header' do # rubocop:todo RSpec/ContextWording 3 | it 'returns 401' do 4 | send(verb, path, nil) 5 | expect_status(:unauthorized) 6 | expect_json('errors.0', 'Invalid token') 7 | end 8 | end 9 | 10 | context 'empty header' do # rubocop:todo RSpec/ContextWording 11 | it 'returns 401' do 12 | send(verb, path, nil, 'Authorization' => '') 13 | expect_status(:unauthorized) 14 | expect_json('errors.0', 'Invalid token') 15 | end 16 | end 17 | 18 | context 'nonexistent token' do # rubocop:todo RSpec/ContextWording 19 | it 'returns 401' do 20 | send(verb, path, nil, 'Authorization' => 'Token wtf') 21 | expect_status(:unauthorized) 22 | expect_json('errors.0', 'Invalid token') 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20250902034147_add_envelope_ceterms_ctid_and_envelope_community_id_to_versions.rb: -------------------------------------------------------------------------------- 1 | class AddEnvelopeCetermsCtidAndEnvelopeCommunityIdToVersions < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :versions, :envelope_ceterms_ctid, :string 4 | add_index :versions, :envelope_ceterms_ctid 5 | 6 | add_reference :versions, :envelope_community, foreign_key: { on_delete: :cascade } 7 | 8 | reversible do |dir| 9 | dir.up do 10 | ActiveRecord::Base.connection.execute(<<~COMMAND) 11 | UPDATE versions 12 | SET envelope_ceterms_ctid = envelopes.envelope_ceterms_ctid, 13 | envelope_community_id = envelopes.envelope_community_id 14 | FROM envelopes 15 | WHERE versions.item_id = envelopes.id 16 | AND versions.item_type = 'Envelope' 17 | COMMAND 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20190227225740_fix_unique_ctid_index_for_envelopes.rb: -------------------------------------------------------------------------------- 1 | class FixUniqueCtidIndexForEnvelopes < ActiveRecord::Migration[4.2] 2 | def up 3 | remove_index :envelopes, [:envelope_ceterms_ctid] 4 | 5 | execute <<~SQL 6 | CREATE UNIQUE INDEX 7 | index_envelopes_on_envelope_ceterms_ctid 8 | ON 9 | envelopes 10 | USING 11 | btree (lower(envelope_ceterms_ctid)) 12 | WHERE 13 | deleted_at IS NULL 14 | SQL 15 | 16 | execute <<~SQL 17 | UPDATE 18 | envelopes 19 | SET 20 | envelope_ceterms_ctid = lower(envelope_ceterms_ctid) 21 | WHERE 22 | deleted_at is null 23 | SQL 24 | end 25 | 26 | def down 27 | execute "DROP INDEX index_envelopes_on_envelope_ceterms_ctid" 28 | add_index :envelopes, [:envelope_ceterms_ctid], unique: true, where: 'deleted_at is null' 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/services/fetch_graph_resources.rb: -------------------------------------------------------------------------------- 1 | # Fetches subresources from the graphs of envelopes with the given CTIDs 2 | class FetchGraphResources 3 | def self.call(ctids, envelope_community:) 4 | relation = Envelope 5 | .not_deleted 6 | # rubocop:todo Layout/LineLength 7 | .joins("CROSS JOIN LATERAL jsonb_array_elements(processed_resource->'@graph') AS graph(resource)") 8 | # rubocop:enable Layout/LineLength 9 | .where(envelope_ceterms_ctid: ctids) 10 | 11 | relation = relation 12 | .where("graph.resource->'ceterms:ctid' IS NULL") 13 | .or(relation.where("graph.resource->>'ceterms:ctid' NOT IN (?)", ctids)) 14 | 15 | relation = relation.where(envelope_community_id: envelope_community.id) if envelope_community 16 | 17 | relation.pluck('graph.resource').map { |r| JSON(r) } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/app-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: main-app-ingress 5 | annotations: 6 | # NGINX Ingress Controller annotations 7 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # HTTP → HTTPS 8 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 9 | nginx.ingress.kubernetes.io/proxy-body-size: "10m" 10 | spec: 11 | ingressClassName: nginx 12 | tls: 13 | - hosts: 14 | - sandbox.credentialengineregistry.org 15 | secretName: sandbox-credentialengineregistry-org-tls 16 | rules: 17 | - host: sandbox.credentialengineregistry.org 18 | http: 19 | paths: 20 | - path: / 21 | pathType: Prefix 22 | backend: 23 | service: 24 | name: main-app 25 | port: 26 | number: 9292 27 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/app-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: main-app-ingress 5 | annotations: 6 | # NGINX Ingress Controller annotations 7 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # HTTP → HTTPS 8 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 9 | nginx.ingress.kubernetes.io/proxy-body-size: "10m" 10 | spec: 11 | ingressClassName: nginx 12 | tls: 13 | - hosts: 14 | - staging.credentialengineregistry.org 15 | secretName: staging-credentialengineregistry-org-tls 16 | rules: 17 | - host: staging.credentialengineregistry.org 18 | http: 19 | paths: 20 | - path: / 21 | pathType: Prefix 22 | backend: 23 | service: 24 | name: main-app 25 | port: 26 | number: 9292 27 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/app-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: main-app-config 5 | namespace: credreg-staging 6 | data: 7 | POSTGRESQL_DATABASE: credential_registry_production 8 | POSTGRESQL_USERNAME: credential_registry_production 9 | RACK_ENV: staging 10 | DOCKER_ENV: "true" 11 | ENVELOPE_GRAPHS_BUCKET: cer-envelope-graphs-staging 12 | ENVELOPE_DOWNLOADS_BUCKET: cer-envelope-downloads 13 | IAM_COMMUNITY_ROLE_ADMIN: ROLE_ADMINISTRATOR 14 | IAM_COMMUNITY_ROLE_READEE: ROLE_READER 15 | IAM_COMMUNITY_ROLE_PUBLISHER: ROLE_PUBLISHER 16 | IAM_COMMUNITY_CLAIM_NAME: community_name 17 | IAM_CLIENT_ID: RegistryAPI 18 | IAM_URL: https://test-ce-kc-002.credentialengine.org/realms/CE-Test 19 | IAM_CLIENT: TestStagingRegistryAPI 20 | AIRBRAKE_PROJECT_ID: '270205' 21 | SIDEKIQ_CONCURRENCY: '10' 22 | ELASTICSEARCH_ADDRESS: http://elasticsearch:9200 -------------------------------------------------------------------------------- /db/migrate/20180301172831_add_ctid_to_organizations.rb: -------------------------------------------------------------------------------- 1 | class AddCtidToOrganizations < ActiveRecord::Migration[4.2] 2 | class Organization < ActiveRecord::Base 3 | end 4 | 5 | def change 6 | ActiveRecord::Base.transaction do 7 | ActiveRecord::Base.connection.execute('LOCK organizations IN SHARE ROW EXCLUSIVE MODE') 8 | 9 | add_column :organizations, :_ctid, :string 10 | add_index :organizations, :_ctid, unique: true 11 | 12 | 13 | Organization.where('_ctid is null').each do |o| 14 | o.update_column(:_ctid, "ce-#{SecureRandom.uuid}") 15 | end 16 | 17 | change_column_null :organizations, :_ctid, false 18 | end 19 | 20 | # remove uniqueness constraint from name field since we're now using _ctid 21 | # as the main identifier 22 | 23 | remove_index :organizations, name: 'index_organizations_on_name' 24 | add_index :organizations, :name 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/db-migrate-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | generateName: db-migrate- 5 | namespace: credreg-prod 6 | labels: 7 | app: main-app 8 | spec: 9 | backoffLimit: 1 10 | activeDeadlineSeconds: 900 11 | ttlSecondsAfterFinished: 600 12 | template: 13 | metadata: 14 | labels: 15 | app: main-app 16 | spec: 17 | serviceAccountName: main-app-service-account 18 | restartPolicy: Never 19 | containers: 20 | - name: db-migrate 21 | image: 996810415034.dkr.ecr.us-east-1.amazonaws.com/registry:production 22 | imagePullPolicy: Always 23 | command: ["/bin/bash","-lc","bundle exec rake db:migrate RACK_ENV=production"] 24 | envFrom: 25 | - secretRef: 26 | name: app-secrets 27 | - configMapRef: 28 | name: main-app-config 29 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # Represents a particular user of both admin and publisher accounts 2 | class User < ActiveRecord::Base 3 | include AttributeNormalizer 4 | 5 | belongs_to :admin 6 | belongs_to :publisher 7 | has_many :auth_tokens 8 | 9 | validates :email, presence: true, uniqueness: { case_sensitive: false } 10 | validate :account_presence 11 | 12 | normalize_attribute :email do |value| 13 | value.is_a?(String) ? value.gsub(/[[:space:]]/, '') : value 14 | end 15 | 16 | after_create :create_auth_token! 17 | 18 | def admin? 19 | admin.present? 20 | end 21 | 22 | def auth_token 23 | auth_tokens.first 24 | end 25 | 26 | def create_auth_token! 27 | auth_tokens.create! 28 | end 29 | 30 | private 31 | 32 | def account_presence 33 | return if admin.present? || publisher.present? 34 | 35 | errors.add(:base, 'Either admin or publisher must be present') 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/db-migrate-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | generateName: db-migrate- 5 | namespace: credreg-sandbox 6 | labels: 7 | app: main-app 8 | spec: 9 | backoffLimit: 1 10 | activeDeadlineSeconds: 900 11 | ttlSecondsAfterFinished: 600 12 | template: 13 | metadata: 14 | labels: 15 | app: main-app 16 | spec: 17 | serviceAccountName: main-app-service-account 18 | restartPolicy: Never 19 | containers: 20 | - name: db-migrate 21 | image: 996810415034.dkr.ecr.us-east-1.amazonaws.com/registry:sandbox 22 | imagePullPolicy: Always 23 | command: ["/bin/bash","-lc","bundle exec rake db:migrate RACK_ENV=production"] 24 | envFrom: 25 | - secretRef: 26 | name: app-secrets 27 | - configMapRef: 28 | name: main-app-config 29 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/db-migrate-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | generateName: db-migrate- 5 | namespace: credreg-staging 6 | labels: 7 | app: main-app 8 | spec: 9 | backoffLimit: 1 10 | activeDeadlineSeconds: 900 11 | ttlSecondsAfterFinished: 600 12 | template: 13 | metadata: 14 | labels: 15 | app: main-app 16 | spec: 17 | serviceAccountName: main-app-service-account 18 | restartPolicy: Never 19 | containers: 20 | - name: db-migrate 21 | image: 996810415034.dkr.ecr.us-east-1.amazonaws.com/registry:staging 22 | imagePullPolicy: Always 23 | command: ["/bin/bash","-lc","bundle exec rake db:migrate RACK_ENV=production"] 24 | envFrom: 25 | - secretRef: 26 | name: app-secrets 27 | - configMapRef: 28 | name: main-app-config 29 | -------------------------------------------------------------------------------- /spec/factories/envelope_downloads.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :envelope_download do 3 | enqueued_at { Time.current.change(usec: 0) } 4 | # rubocop:todo FactoryBot/FactoryAssociationWithStrategy 5 | envelope_community { create(:envelope_community, :with_random_name) } 6 | # rubocop:enable FactoryBot/FactoryAssociationWithStrategy 7 | 8 | trait :failed do 9 | finished_at { Time.current.change(usec: 0) } 10 | internal_error_message { Faker::Lorem.sentence } 11 | started_at { Time.current.change(usec: 0) } 12 | status { :finished } 13 | end 14 | 15 | trait :finished do 16 | finished_at { Time.current.change(usec: 0) } 17 | started_at { Time.current.change(usec: 0) } 18 | status { :finished } 19 | end 20 | 21 | trait :in_progress do 22 | started_at { Time.current.change(usec: 0) } 23 | status { :in_progress } 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/jobs/extract_envelope_resources_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'extract_envelope_resources_job' 2 | require 'index_envelope_job' 3 | 4 | RSpec.describe ExtractEnvelopeResourcesJob do 5 | describe '#perform' do 6 | let(:envelope) { create(:envelope) } 7 | 8 | before do 9 | # rubocop:todo RSpec/MessageSpies 10 | # rubocop:todo RSpec/ExpectInHook 11 | expect(ExtractEnvelopeResources).to receive(:call).with(envelope:) 12 | # rubocop:enable RSpec/ExpectInHook 13 | # rubocop:enable RSpec/MessageSpies 14 | # rubocop:todo RSpec/MessageSpies 15 | # rubocop:todo RSpec/ExpectInHook 16 | expect(IndexEnvelopeJob).to receive(:perform_later).with(envelope.id) 17 | # rubocop:enable RSpec/ExpectInHook 18 | # rubocop:enable RSpec/MessageSpies 19 | end 20 | 21 | it 'calls ExtractEnvelopeResources' do 22 | described_class.new.perform(envelope.id) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /terraform/modules/eks/iamrole-for-eks-cluster.tf: -------------------------------------------------------------------------------- 1 | # Create IAM Role 2 | resource "aws_iam_role" "eks_master_role" { 3 | name = "${var.cluster_name}-eks-master-role" 4 | 5 | assume_role_policy = < envelope_community.name 13 | ) 14 | end 15 | 16 | it 'builds a new envelope and indexes its resources' do 17 | created_envelope_id = nil 18 | 19 | allow(ExtractEnvelopeResourcesJob).to receive(:perform_later) do |id| 20 | created_envelope_id = id 21 | end 22 | 23 | created, = described_class.new( 24 | envelope, 25 | update_if_exists: true 26 | ).build 27 | expect(created.id).to eq(created_envelope_id) 28 | expect(created.persisted?).to be true 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /fixtures/schemas/schemaorg/_creative_work.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "schema:type": { "enum": [ "CreativeWork" ] }, 5 | "<%=prop 'description' %>": { 6 | "type": "string", 7 | "description": "A description of the resource" 8 | }, 9 | "<%=prop 'image' %>": { 10 | "type": "string", 11 | "format": "uri", 12 | "description": "Url of an image of the item." 13 | }, 14 | "<%=prop 'name' %>": { 15 | "type": "string", 16 | "description": "The name of the resource" 17 | }, 18 | "<%=prop 'url' %>": { 19 | "type": "string", 20 | "format": "uri", 21 | "description": "URL of the resource" 22 | }, 23 | "<%=prop 'contributor' %>": { 24 | "description": "A secondary contributor to the CreativeWork." 25 | }, 26 | "<%=prop 'creator' %>": { 27 | "description": "The creator/author of this CreativeWork." 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /db/migrate/20250830180848_add_unique_index_on_envelope_community_id_to_envelope_downloads.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexOnEnvelopeCommunityIdToEnvelopeDownloads < ActiveRecord::Migration[8.0] 2 | def change 3 | ActiveRecord::Base.transaction do 4 | reversible do |dir| 5 | dir.up do 6 | ActiveRecord::Base.connection.execute(<<~COMMAND) 7 | DELETE FROM envelope_downloads 8 | WHERE created_at NOT IN ( 9 | SELECT max_created_at 10 | FROM ( 11 | SELECT MAX(created_at ) as max_created_at 12 | FROM envelope_downloads 13 | GROUP BY envelope_community_id 14 | ) AS t 15 | ); 16 | COMMAND 17 | end 18 | end 19 | 20 | remove_index :envelope_downloads, :envelope_community_id 21 | add_index :envelope_downloads, :envelope_community_id, unique: true 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/extensions/learning_registry_resources.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'registry_metadata' 3 | 4 | # LearningRegistry specific behavior for resource envelopes 5 | module LearningRegistryResources 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | validate do 10 | if !skip_lr_metadata_validation? && !registry_metadata.valid? 11 | errors.add :resource, registry_metadata.errors.full_messages 12 | end 13 | end 14 | 15 | def registry_metadata 16 | return unless decoded_resource.registry_metadata 17 | 18 | @registry_metadata ||= RegistryMetadata.new( 19 | decoded_resource.registry_metadata 20 | ) 21 | end 22 | 23 | def from_learning_registry? 24 | envelope_community.name == 'learning_registry' 25 | end 26 | 27 | def skip_lr_metadata_validation? 28 | !from_learning_registry? || paradata? || registry_metadata.nil? 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/jobs/download_envelopes_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DownloadEnvelopesJob do 4 | let(:envelope_download) { create(:envelope_download) } 5 | 6 | describe '#perform' do 7 | context 'without error' do 8 | it 'calls DownloadEnvelopes' do 9 | allow(DownloadEnvelopes).to receive(:call).with(envelope_download:) 10 | described_class.new.perform(envelope_download.id) 11 | end 12 | end 13 | 14 | context 'with error' do 15 | let(:error) { StandardError.new } 16 | 17 | it 'logs error' do 18 | allow(Airbrake).to receive(:notify) 19 | .with(error, envelope_download_id: envelope_download.id) 20 | 21 | allow(DownloadEnvelopes).to receive(:call) 22 | .with(envelope_download:) 23 | .and_raise(error) 24 | 25 | expect { described_class.new.perform(envelope_download.id) }.to raise_error(error) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /bin/sidekiq: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sidekiq' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | require "active_support/core_ext/object/blank" 29 | 30 | load Gem.bin_path("sidekiq", "sidekiq") 31 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | host: <%= ENV['POSTGRESQL_ADDRESS'] %> 4 | username: <%= ENV['POSTGRESQL_USERNAME'] %> 5 | database: <%= ENV['POSTGRESQL_DATABASE'] %> 6 | password: <%= ENV['POSTGRESQL_PASSWORD'] %> 7 | port: <%= ENV.fetch('POSTGRESQL_PORT', 5432) %> 8 | pool: <%= ENV.fetch('SIDEKIQ_CONCURRENCY', 10).to_i + 1 %> 9 | encoding: utf8 10 | 11 | development: 12 | <<: *default 13 | 14 | test: 15 | <<: *default 16 | 17 | staging: 18 | <<: *default 19 | 20 | sandbox: 21 | <<: *default 22 | 23 | production: 24 | <<: *default 25 | 26 | goopen_migration: 27 | adapter: postgresql 28 | host: <%= ENV['GOOPEN_DB_ADDRESS'] %> 29 | username: <%= ENV['GOOPEN_DB_USERNAME'] %> 30 | database: <%= ENV['GOOPEN_DB_DATABASE'] %> 31 | password: <%= ENV['GOOPEN_DB_PASSWORD'] %> 32 | pool: 10 33 | encoding: utf8 34 | variables: 35 | tcp_keepalives_idle: 60 36 | tcp_keepalives_interval: 60 37 | tcp_keepalives_count: 100 38 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/app-hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: main-app 5 | namespace: credreg-prod 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: main-app 11 | minReplicas: 2 12 | maxReplicas: 6 13 | metrics: 14 | - type: Resource 15 | resource: 16 | name: cpu 17 | target: 18 | type: Utilization 19 | averageUtilization: 70 20 | 21 | --- 22 | apiVersion: autoscaling/v2 23 | kind: HorizontalPodAutoscaler 24 | metadata: 25 | name: worker-app 26 | namespace: credreg-prod 27 | spec: 28 | scaleTargetRef: 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | name: worker-app 32 | minReplicas: 2 33 | maxReplicas: 10 34 | metrics: 35 | - type: Resource 36 | resource: 37 | name: cpu 38 | target: 39 | type: Utilization 40 | averageUtilization: 70 41 | 42 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | config.vm.provider :virtualbox do |vb| 6 | vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'on'] 7 | vb.customize ['modifyvm', :id, '--natdnsproxy1', 'on'] 8 | vb.customize ['modifyvm', :id, '--memory', 2048] 9 | vb.customize ['modifyvm', :id, '--cpus', 2] 10 | end 11 | 12 | config.vm.box = 'ubuntu/trusty64' 13 | 14 | config.vm.provision :shell, inline: <<-SHELL 15 | if [ ! -f ~/.runonce ] 16 | then 17 | sudo apt-get install --reinstall -y language-pack-en 18 | sudo locale-gen "en_US.UTF-8" 19 | sudo dpkg-reconfigure locales 20 | echo 'LC_ALL="en_US.UTF-8"' | sudo tee -a /etc/default/locale 21 | echo 'LANG="en_US.UTF-8"' | sudo tee -a /etc/default/locale 22 | touch ~/.runonce 23 | fi 24 | SHELL 25 | 26 | #-----------------Network 27 | # App server 28 | config.vm.network :forwarded_port, guest: 9292, host: 9292 29 | end 30 | -------------------------------------------------------------------------------- /fixtures/schemas/schemaorg/_address.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "schema:type": { "enum": [ "PostalAddress" ] }, 5 | "<%=prop 'addressCountry' %>": { 6 | "type": "string", 7 | "description": "The country. For example, USA. You can also provide the two-letter ISO 3166-1 alpha-2 country code (https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)" 8 | }, 9 | "<%=prop 'addressLocality' %>": { 10 | "type": "string", 11 | "description": "The locality. For example, Mountain View." 12 | }, 13 | "<%=prop 'addressRegion' %>": { 14 | "type": "string", 15 | "description": "The region. For example, CA." 16 | }, 17 | "<%=prop 'postalCode' %>": { 18 | "type": "string", 19 | "description": "The postal code. For example, 94043." 20 | }, 21 | "<%=prop 'streetAddress' %>": { 22 | "type": "string", 23 | "description": "The street address. For example, 1600 Amphitheatre Pkwy." 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/app-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: main-app-config 5 | data: 6 | POSTGRESQL_DATABASE: credential_registry_sandbox 7 | POSTGRESQL_USERNAME: credential_registry_sandbox 8 | RACK_ENV: sandbox 9 | DOCKER_ENV: "true" 10 | ENVELOPE_GRAPHS_BUCKET: cer-envelope-graphs-sandbox 11 | ENVELOPE_DOWNLOADS_BUCKET: cer-envelope-downloads 12 | IAM_COMMUNITY_ROLE_ADMIN: ROLE_ADMINISTRATOR 13 | IAM_COMMUNITY_ROLE_READEE: ROLE_READER 14 | IAM_COMMUNITY_ROLE_PUBLISHER: ROLE_PUBLISHER 15 | IAM_COMMUNITY_CLAIM_NAME: community_name 16 | IAM_CLIENT_ID: RegistryAPI 17 | IAM_URL: https://test-ce-kc-002.credentialengine.org/realms/CE-Test 18 | IAM_CLIENT: TestsandboxRegistryAPI 19 | AIRBRAKE_PROJECT_ID: '270205' 20 | SIDEKIQ_CONCURRENCY: '10' 21 | API_KEY_VALIDATION_ENDPOINT: https://sandbox.credentialengine.org/accountsAPI/Organization/ValidateCommunityAccess 22 | ELASTICSEARCH_ADDRESS: http://elasticsearch:9200 -------------------------------------------------------------------------------- /app/api/entities/envelope_download.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for EnvelopeDownload 4 | class EnvelopeDownload < Grape::Entity 5 | expose :display_status, as: :status, 6 | documentation: { type: 'string', desc: 'Status of download' } 7 | 8 | expose :enqueued_at, 9 | documentation: { type: 'string', desc: 'When the download was enqueued' }, 10 | if: ->(object) { object.pending? } 11 | 12 | expose :finished_at, 13 | documentation: { type: 'string', desc: 'When the download finished' }, 14 | if: ->(object) { object.finished? } 15 | 16 | expose :started_at, 17 | documentation: { type: 'string', desc: 'When the download started' }, 18 | if: ->(object) { object.in_progress? } 19 | 20 | expose :url, 21 | documentation: { type: 'string', desc: 'AWS S3 URL' }, 22 | if: ->(object) { object.finished? } 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/api/entities/version.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for an Envelope Version 4 | class Version < Grape::Entity 5 | expose :head, 6 | documentation: { type: 'boolean', 7 | desc: 'Tells if it\'s the current version' } 8 | expose :event, 9 | documentation: { type: 'string', 10 | desc: 'What change caused the new version' } 11 | expose :created_at, 12 | documentation: { type: 'datetime', 13 | desc: 'When the version was created' } 14 | expose :whodunnit, 15 | as: :actor, 16 | documentation: { type: 'string', 17 | desc: 'Who performed the changes' } 18 | expose :url, 19 | documentation: { type: 'string', desc: 'Version URL' } 20 | 21 | def created_at 22 | Time.zone.parse(object.created_at) if object.created_at? 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20190919121231_change_scope_of_ctid_index_in_envelopes.rb: -------------------------------------------------------------------------------- 1 | require 'migration_helpers' 2 | 3 | class ChangeScopeOfCtidIndexInEnvelopes < ActiveRecord::Migration[4.2] 4 | include MigrationHelpers 5 | 6 | def up 7 | execute 'DROP INDEX index_envelopes_on_envelope_ceterms_ctid' 8 | 9 | execute <<~SQL 10 | CREATE UNIQUE INDEX 11 | index_envelopes_on_envelope_community_id_and_envelope_ceterms_ctid 12 | ON 13 | envelopes (envelope_community_id, lower(envelope_ceterms_ctid)) 14 | WHERE 15 | deleted_at IS NULL 16 | SQL 17 | end 18 | 19 | def down 20 | execute 'DROP INDEX index_envelopes_on_envelope_community_id_and_envelope_ceterms_ctid' 21 | 22 | delete_envelopes_with_duplicated_ctids 23 | 24 | execute <<~SQL 25 | CREATE UNIQUE INDEX 26 | index_envelopes_on_envelope_ceterms_ctid 27 | ON 28 | envelopes (lower(envelope_ceterms_ctid)) 29 | WHERE 30 | deleted_at IS NULL 31 | SQL 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile.test 8 | environment: 9 | DATABASE_URL: postgresql://postgres:postgres@postgres:5432/metadataregistry_text 10 | RACK_ENV: test 11 | SECRET_KEY_BASE: dummy-value 12 | GIT_AUTHOR_NAME: "${GIT_AUTHOR_NAME:-Local Tester}" 13 | GIT_AUTHOR_EMAIL: "${GIT_AUTHOR_EMAIL:-tester@example.com}" 14 | volumes: 15 | - .:/app # live-mount the source for fast feedback 16 | depends_on: 17 | postgres: 18 | condition: service_healthy 19 | command: ["bundle","exec","rspec"] 20 | 21 | postgres: 22 | image: postgres:16 23 | environment: 24 | POSTGRES_DB: cr_development 25 | POSTGRES_USER: postgres 26 | POSTGRES_PASSWORD: postgres 27 | ports: 28 | - "5432:5432" 29 | healthcheck: 30 | test: ["CMD-SHELL", "pg_isready -U postgres"] 31 | interval: 10s 32 | timeout: 5s 33 | retries: 5 -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-prod/debug-aws-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: debug-aws-cli 5 | namespace: credreg-prod 6 | labels: 7 | app: debug-aws-cli 8 | spec: 9 | serviceAccountName: main-app-service-account 10 | restartPolicy: Never 11 | priorityClassName: prod-high 12 | nodeSelector: 13 | env: production 14 | tolerations: 15 | - key: "env" 16 | operator: "Equal" 17 | value: "production" 18 | effect: "NoSchedule" 19 | containers: 20 | - name: awscli 21 | image: public.ecr.aws/aws-cli/aws-cli:latest 22 | imagePullPolicy: IfNotPresent 23 | command: ["sh", "-lc", "echo Ready; sleep 3600"] 24 | env: 25 | - name: AWS_REGION 26 | value: us-east-1 27 | - name: AWS_DEFAULT_REGION 28 | value: us-east-1 29 | resources: 30 | requests: 31 | cpu: "50m" 32 | memory: "64Mi" 33 | limits: 34 | cpu: "500m" 35 | memory: "256Mi" 36 | 37 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/debug-aws-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: debug-aws-cli 5 | namespace: credreg-sandbox 6 | labels: 7 | app: debug-aws-cli 8 | spec: 9 | serviceAccountName: main-app-service-account 10 | restartPolicy: Never 11 | priorityClassName: sandbox-low 12 | nodeSelector: 13 | env: sandbox 14 | tolerations: 15 | - key: "env" 16 | operator: "Equal" 17 | value: "sandbox" 18 | effect: "NoSchedule" 19 | containers: 20 | - name: awscli 21 | image: public.ecr.aws/aws-cli/aws-cli:latest 22 | imagePullPolicy: IfNotPresent 23 | command: ["sh", "-lc", "echo Ready; sleep 3600"] 24 | env: 25 | - name: AWS_REGION 26 | value: us-east-1 27 | - name: AWS_DEFAULT_REGION 28 | value: us-east-1 29 | resources: 30 | requests: 31 | cpu: "50m" 32 | memory: "64Mi" 33 | limits: 34 | cpu: "500m" 35 | memory: "256Mi" 36 | 37 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-staging/debug-aws-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: debug-aws-cli 5 | namespace: credreg-staging 6 | labels: 7 | app: debug-aws-cli 8 | spec: 9 | serviceAccountName: main-app-service-account 10 | restartPolicy: Never 11 | priorityClassName: staging-medium 12 | nodeSelector: 13 | env: staging 14 | tolerations: 15 | - key: "env" 16 | operator: "Equal" 17 | value: "staging" 18 | effect: "NoSchedule" 19 | containers: 20 | - name: awscli 21 | image: public.ecr.aws/aws-cli/aws-cli:latest 22 | imagePullPolicy: IfNotPresent 23 | command: ["sh", "-lc", "echo Ready; sleep 3600"] 24 | env: 25 | - name: AWS_REGION 26 | value: us-east-1 27 | - name: AWS_DEFAULT_REGION 28 | value: us-east-1 29 | resources: 30 | requests: 31 | cpu: "50m" 32 | memory: "64Mi" 33 | limits: 34 | cpu: "500m" 35 | memory: "256Mi" 36 | 37 | -------------------------------------------------------------------------------- /app/services/indexer_stats.rb: -------------------------------------------------------------------------------- 1 | # Returns the status of the envelope's indexing job if it's enqueued 2 | class IndexerStats 3 | attr_reader :community_name 4 | 5 | def initialize(community_name) 6 | @community_name = community_name 7 | end 8 | 9 | def self.call(community_name) 10 | new(community_name).counts 11 | end 12 | 13 | def counts 14 | { 15 | enqueued_jobs: count_envelopes(enqueued_jobs), 16 | in_progress_jobs: count_envelopes(in_progress_jobs) 17 | } 18 | end 19 | 20 | private 21 | 22 | def count_envelopes(jobs) 23 | ids = jobs.map { it.item.dig('args', 0, 'arguments', 0) } 24 | Envelope.in_community(community_name).where(id: ids).count 25 | end 26 | 27 | def in_progress_jobs 28 | @in_progress_jobs ||= Sidekiq::Workers.new.map { it.last.job } 29 | end 30 | 31 | def enqueued_jobs 32 | @enqueued_jobs ||= Sidekiq::Queue 33 | .new('default') 34 | .select { it.item.fetch('wrapped') == 'IndexEnvelopeJob' } 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/services/validate_api_key.rb: -------------------------------------------------------------------------------- 1 | # Validates an API key by calling a validation endpoint in the related account site 2 | class ValidateApiKey 3 | # rubocop:todo Metrics/MethodLength 4 | def self.call(value, community) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength 5 | cache_key = ['api_keys', value, community.name] 6 | expires_in = ENV.fetch('API_KEY_EXPIRATION_PERIOD', 3_600).to_i 7 | 8 | MR.cache.fetch(cache_key, expires_in: expires_in.seconds) do 9 | response = RestClient.get( 10 | ENV.fetch('API_KEY_VALIDATION_ENDPOINT'), 11 | params: { apikey: value, community: community.name } 12 | ) 13 | 14 | body = JSON(response.body) 15 | body.fetch('valid', false) 16 | end 17 | rescue RestClient::Exception => e 18 | message = "Validation failed for API key #{value} " \ 19 | "in #{community.name} community: #{e.message}" 20 | 21 | MR.logger.error(message) 22 | Airbrake.notify(message) 23 | false 24 | end 25 | # rubocop:enable Metrics/MethodLength 26 | end 27 | -------------------------------------------------------------------------------- /config/arel_nodes_cte.rb: -------------------------------------------------------------------------------- 1 | # Monkey-patched to generate materialized CTEs by default: 2 | # https://github.com/rails/rails/blob/v7.1.2/activerecord/lib/arel/nodes/cte.rb 3 | module Arel 4 | module Nodes 5 | class Cte < Arel::Nodes::Binary # rubocop:todo Style/Documentation 6 | alias name left 7 | alias relation right 8 | attr_reader :materialized 9 | 10 | def initialize(name, relation, materialized: nil) # rubocop:todo Lint/UnusedMethodArgument 11 | super(name, relation) 12 | @materialized = false 13 | end 14 | 15 | def hash 16 | [name, relation, materialized].hash 17 | end 18 | 19 | def eql?(other) 20 | self.class == other.class && 21 | name == other.name && 22 | relation == other.relation && 23 | materialized == other.materialized 24 | end 25 | alias == eql? 26 | 27 | def to_cte 28 | self 29 | end 30 | 31 | def to_table 32 | Arel::Table.new(name) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /db/migrate/20180727234351_remove_fts_from_envelopes.rb: -------------------------------------------------------------------------------- 1 | class RemoveFtsFromEnvelopes < ActiveRecord::Migration[4.2] 2 | def up 3 | remove_column :envelopes, :fts_tsearch, :text 4 | remove_column :envelopes, :fts_trigram, :text 5 | remove_column :envelopes, :fts_tsearch_tsv, :tsvector 6 | 7 | execute <<-SQL 8 | DROP TRIGGER fts_tsvector_update ON envelopes 9 | SQL 10 | end 11 | 12 | def down 13 | add_column :envelopes, :fts_tsearch, :text 14 | add_column :envelopes, :fts_tsearch_tsv, :tsvector 15 | add_column :envelopes, :fts_trigram, :text 16 | add_index :envelopes, :fts_tsearch_tsv, using: "gin" 17 | 18 | execute <<-SQL 19 | CREATE INDEX envelopes_fts_trigram_idx ON envelopes 20 | USING gin (fts_trigram gin_trgm_ops) 21 | SQL 22 | 23 | execute <<-SQL 24 | CREATE TRIGGER fts_tsvector_update BEFORE INSERT OR UPDATE 25 | ON envelopes FOR EACH ROW EXECUTE PROCEDURE 26 | tsvector_update_trigger(fts_tsearch_tsv, 'pg_catalog.simple', fts_tsearch); 27 | SQL 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/services/ctdl_subclasses_resolver.rb: -------------------------------------------------------------------------------- 1 | class CtdlSubclassesResolver # rubocop:todo Style/Documentation 2 | SUBCLASSES_MAP_FILE = MR.root_path.join('fixtures', 'subclasses_map.json') 3 | 4 | attr_reader :envelope_community_config, :include_root, :root_class 5 | 6 | def initialize(envelope_community:, root_class:, include_root: true) 7 | @envelope_community_config = envelope_community.config 8 | @include_root = include_root 9 | @root_class = root_class 10 | end 11 | 12 | def subclasses 13 | @subclasses ||= collect_subclasses(initial_map_item) + 14 | (include_root ? [root_class] : []) 15 | end 16 | 17 | def initial_map_item 18 | ctdl_subclasses_map[root_class] || {} 19 | end 20 | 21 | def collect_subclasses(start) 22 | start.keys + start.values.flat_map { collect_subclasses(it) } 23 | end 24 | 25 | def ctdl_subclasses_map 26 | @ctdl_subclasses_map ||= envelope_community_config.fetch( 27 | 'subclasses_map', 28 | JSON.parse(File.read(SUBCLASSES_MAP_FILE)) 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /bin/delete_resource: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # helper script to delete resource envelope 4 | 5 | require 'jwt' 6 | 7 | if ARGV.length != 4 8 | puts 'USAGE: delete_resource ENVELOPE_ID PRIVATE_KEY PUBLIC_KEY TARGET_SERVER' 9 | exit 10 | end 11 | 12 | ENVELOPE_ID = ARGV[0] 13 | PRIVATE_KEY = ARGV[1] 14 | PUBLIC_KEY = ARGV[2] 15 | TARGET_SERVER = ARGV[3] 16 | 17 | ENVELOPES_URL = "#{TARGET_SERVER}/envelopes" 18 | 19 | priv_key = OpenSSL::PKey::RSA.new(File.read(PRIVATE_KEY)) 20 | delete_envelope = { 21 | "envelope_type": "resource_data", 22 | "envelope_version": "1.0.0", 23 | "envelope_community": "ce_registry", 24 | "envelope_id": ENVELOPE_ID, 25 | "delete_token": JWT.encode({"delete": true}, priv_key, 'RS256'), 26 | "delete_token_format": "json", 27 | "delete_token_encoding": "jwt", 28 | "delete_token_public_key": File.read(PUBLIC_KEY) 29 | } 30 | 31 | fname = "./tmp/envelope_DELETE_#{ENVELOPE_ID}.json" 32 | File.write fname, delete_envelope.to_json 33 | 34 | puts `curl -s -XPUT #{ENVELOPES_URL} -d @#{fname} -H "Content-Type: application/json"` 35 | -------------------------------------------------------------------------------- /docs/samples/lua/jwt_encode.lua: -------------------------------------------------------------------------------- 1 | -- Usage: lua jwt_encode.lua path/to/json/file.json path/to/private_key/pem 2 | 3 | -- luarocks install luacrypto 4 | -- luarocks install jwt 5 | local cjson = require 'cjson' 6 | local crypto = require 'crypto' 7 | local jwt = require 'jwt' 8 | 9 | -- read whole file into a string 10 | function read_file(filepath) 11 | local f = io.open(filepath, "rb") 12 | local content = f:read("*all") 13 | f:close() 14 | return content 15 | end 16 | 17 | -- encode JWT passing the data table and the key path 18 | function jwt_encode(data, key_path) 19 | local pkey = crypto.pkey.from_pem(read_file(key_path), true) 20 | local token, _ = jwt.encode(data, { 21 | alg = "RS256", 22 | keys = { private = pkey } 23 | }) 24 | return token 25 | end 26 | 27 | -- get json data from file 28 | local json_content = read_file(arg[1]) 29 | local data = cjson.decode(json_content) 30 | 31 | -- get private key path 32 | local key_path = arg[2] 33 | 34 | -- encode our JWT token 35 | local encoded_token = jwt_encode(data, key_path) 36 | 37 | print(encoded_token) 38 | -------------------------------------------------------------------------------- /spec/support/custom_matchers/be_bnode.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :be_bnode do 2 | match do |actual| 3 | return false unless actual.is_a?(String) 4 | return false unless actual.start_with?('_:') 5 | 6 | UUID.validate(actual[2..(actual.size - 1)]).present? 7 | end 8 | 9 | description do 10 | "be a valid bnode (i.e. a UUID with '_:' prefix)" 11 | end 12 | 13 | failure_message do |actual| 14 | if !actual.is_a?(String) 15 | "expected #{actual.inspect} to be a String, but it was #{actual.class}" 16 | elsif !actual.start_with?('_:') 17 | "expected #{actual.inspect} to start with '_:', but it starts with '#{actual[0..1]}'" 18 | else 19 | uuid_part = actual[2..(actual.size - 1)] 20 | # rubocop:todo Layout/LineLength 21 | "expected #{actual.inspect} to have a valid UUID after the '_:' prefix, but '#{uuid_part}' is not a valid UUID" 22 | # rubocop:enable Layout/LineLength 23 | end 24 | end 25 | 26 | failure_message_when_negated do |actual| 27 | "expected #{actual.inspect} not to be a bnode, but it was" 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /terraform/modules/eks/iamrole-for-eks-nodegroup.tf: -------------------------------------------------------------------------------- 1 | # IAM Role for EKS Node Group 2 | resource "aws_iam_role" "eks_nodegroup_role" { 3 | name = "${var.cluster_name}-eks-nodegroup-role" 4 | 5 | assume_role_policy = jsonencode({ 6 | Statement = [{ 7 | Action = "sts:AssumeRole" 8 | Effect = "Allow" 9 | Principal = { 10 | Service = "ec2.amazonaws.com" 11 | } 12 | }] 13 | Version = "2012-10-17" 14 | }) 15 | } 16 | 17 | resource "aws_iam_role_policy_attachment" "eks-AmazonEKSWorkerNodePolicy" { 18 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" 19 | role = aws_iam_role.eks_nodegroup_role.name 20 | } 21 | 22 | resource "aws_iam_role_policy_attachment" "eks-AmazonEKS_CNI_Policy" { 23 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" 24 | role = aws_iam_role.eks_nodegroup_role.name 25 | } 26 | 27 | resource "aws_iam_role_policy_attachment" "eks-AmazonEC2ContainerRegistryReadOnly" { 28 | policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" 29 | role = aws_iam_role.eks_nodegroup_role.name 30 | } 31 | -------------------------------------------------------------------------------- /app/services/build_node_headers.rb: -------------------------------------------------------------------------------- 1 | # Builds the node headers that are appended to the envelope when it's saved 2 | class BuildNodeHeaders 3 | attr_reader :envelope 4 | 5 | def initialize(envelope) 6 | @envelope = envelope 7 | end 8 | 9 | def headers 10 | { 11 | created_at: envelope.created_at, 12 | updated_at: envelope.updated_at, 13 | deleted_at: envelope.deleted_at, 14 | versions: versions_header 15 | } 16 | end 17 | 18 | def versions_header 19 | envelope.versions.map do |version| 20 | { 21 | head: version.next.blank?, 22 | event: version.event, 23 | created_at: version.created_at, 24 | author: version.whodunnit, 25 | url: version_url(version) 26 | } 27 | end 28 | end 29 | 30 | def version_url(version) 31 | community = envelope.envelope_community.name.dasherize 32 | 33 | if version.next.blank? 34 | "/#{community}/envelopes/#{envelope.envelope_id}" 35 | else 36 | "/#{community}/envelopes/#{envelope.envelope_id}" \ 37 | "/revisions/#{version.next.id}" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /fixtures/schemas/schemaorg/_person.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "schema:type": {"enum": ["Person"]}, 5 | "<%=prop 'description' %>": { 6 | "type": "string", 7 | "description": "A description of the resource" 8 | }, 9 | "<%=prop 'image' %>": { 10 | "type": "string", 11 | "format": "uri", 12 | "description": "Url of an image of the item." 13 | }, 14 | "<%=prop 'name' %>": { 15 | "type": "string", 16 | "description": "The name of the resource" 17 | }, 18 | "<%=prop 'url' %>": { 19 | "type": "string", 20 | "format": "uri", 21 | "description": "URL of the resource" 22 | }, 23 | "<%=prop 'birthDate' %>": { 24 | "description": "Date of birth.", 25 | "type": "string", 26 | "format": "date" 27 | }, 28 | "<%=prop 'email' %>": { 29 | "type": "string", 30 | "pattern": "^$|^.+\\@.+\\..+$", 31 | "description": "E-mail address." 32 | }, 33 | "<%=prop 'telephone' %>": { 34 | "description": "The telephone number.", 35 | "type": "string" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spec/factories/indexed_envelope_resources.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :indexed_envelope_resource do 3 | # rubocop:todo FactoryBot/FactoryAssociationWithStrategy 4 | envelope_resource { create(:envelope_resource, envelope:, resource_id: Envelope.generate_ctid) } 5 | # rubocop:enable FactoryBot/FactoryAssociationWithStrategy 6 | payload { envelope_resource.processed_resource } 7 | 8 | add_attribute('@id') { envelope_resource.processed_resource['@id'] } 9 | add_attribute('@type') { envelope_resource.processed_resource['@type'] } 10 | add_attribute('ceterms:ctid') { envelope_resource.resource_id } 11 | 12 | transient do 13 | envelope do 14 | create( # rubocop:todo FactoryBot/FactoryAssociationWithStrategy 15 | :envelope, 16 | :with_cer_credential, 17 | envelope_community: envelope_community 18 | ) 19 | end 20 | 21 | # rubocop:todo FactoryBot/FactoryAssociationWithStrategy 22 | envelope_community { create(:envelope_community, :with_random_name) } 23 | # rubocop:enable FactoryBot/FactoryAssociationWithStrategy 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/services/generate_envelope_dump.rb: -------------------------------------------------------------------------------- 1 | require 'internet_archive' 2 | 3 | # Generates a Gzip compresed dump file containing the dumped envelope 4 | # transactions 5 | class GenerateEnvelopeDump 6 | attr_reader :date, :community, :provider, :file_name 7 | 8 | DUMPS_PATH = MR.root_path.join('tmp', 'dumps') 9 | 10 | def initialize(date, 11 | community, 12 | provider = InternetArchive.new(community.backup_item)) 13 | @date = date 14 | @community = community 15 | @provider = provider 16 | @file_name = "dump-#{date}.txt.gz" 17 | return if File.directory?(DUMPS_PATH) 18 | 19 | FileUtils.mkdir_p(DUMPS_PATH) 20 | end 21 | 22 | def run 23 | write_dump_to_file 24 | end 25 | 26 | def dump_file 27 | DUMPS_PATH.join(file_name) 28 | end 29 | 30 | private 31 | 32 | def write_dump_to_file 33 | Zlib::GzipWriter.open(dump_file) do |gzip_file| 34 | transactions.each { |transaction| gzip_file.puts(transaction.dump) } 35 | gzip_file.close 36 | end 37 | end 38 | 39 | def transactions 40 | EnvelopeTransaction.in_community(community.name).in_date(date) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /db/migrate/20210513043719_create_indexed_envelope_resource_references.rb: -------------------------------------------------------------------------------- 1 | class CreateIndexedEnvelopeResourceReferences < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :indexed_envelope_resource_references do |t| 4 | t.string :path, null: false 5 | 6 | t.references :resource, 7 | foreign_key: { 8 | on_delete: :cascade, 9 | to_table: :indexed_envelope_resources 10 | }, 11 | index: false, 12 | null: false 13 | 14 | t.string :resource_uri, null: false 15 | t.string :subresource_uri, null: false 16 | 17 | t.foreign_key :indexed_envelope_resources, 18 | column: :resource_uri, 19 | on_delete: :cascade, 20 | primary_key: :'@id' 21 | 22 | t.index %i[path resource_id resource_uri subresource_uri], 23 | name: 'index_indexed_envelope_resource_references', 24 | unique: true 25 | 26 | t.index :subresource_uri, 27 | opclass: { subresource_uri: :gin_trgm_ops }, 28 | using: :gin 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /terraform/modules/envelope_graphs_s3/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | tags = merge(var.common_tags, { 3 | Name = var.bucket_name 4 | purpose = "envelope-graphs" 5 | environment = var.environment 6 | }) 7 | } 8 | 9 | resource "aws_s3_bucket" "this" { 10 | bucket = var.bucket_name 11 | tags = local.tags 12 | } 13 | 14 | resource "aws_s3_bucket_ownership_controls" "this" { 15 | bucket = aws_s3_bucket.this.id 16 | rule { 17 | object_ownership = "BucketOwnerEnforced" 18 | } 19 | } 20 | 21 | resource "aws_s3_bucket_public_access_block" "this" { 22 | bucket = aws_s3_bucket.this.id 23 | 24 | block_public_acls = true 25 | block_public_policy = true 26 | ignore_public_acls = true 27 | restrict_public_buckets = true 28 | } 29 | 30 | resource "aws_s3_bucket_server_side_encryption_configuration" "this" { 31 | bucket = aws_s3_bucket.this.id 32 | 33 | rule { 34 | apply_server_side_encryption_by_default { 35 | sse_algorithm = "AES256" 36 | } 37 | } 38 | } 39 | 40 | resource "aws_s3_bucket_versioning" "this" { 41 | bucket = aws_s3_bucket.this.id 42 | versioning_configuration { 43 | status = "Enabled" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Select version of overcommit and the other tools from Gemfile 3 | # 4 | gemfile: Gemfile 5 | 6 | # 7 | # Hooks that are run against every commit message after a user has written it. 8 | # 9 | CommitMsg: 10 | HardTabs: 11 | enabled: true 12 | 13 | SingleLineSubject: 14 | enabled: true 15 | 16 | # 17 | # Hooks that are run after `git commit` is executed, before the commit message 18 | # editor is displayed. 19 | # 20 | PreCommit: 21 | ALL: 22 | exclude: 23 | - 'public/**/*' 24 | - 'spec/support/cassettes/**/*' 25 | 26 | BundleCheck: 27 | enabled: true 28 | 29 | LocalPathsInGemfile: 30 | enabled: true 31 | 32 | ExecutePermissions: 33 | enabled: true 34 | exclude: 35 | - bin/* 36 | 37 | RuboCop: 38 | enabled: true 39 | exclude: 40 | - 'db/schema.rb' 41 | - 'db/migrate/*.rb' 42 | command: ['bundle', 'exec', 'rubocop', '--format', 'json', '--out', 'rubocop-report.json'] 43 | 44 | TrailingWhitespace: 45 | enabled: true 46 | 47 | YamlSyntax: 48 | enabled: true 49 | 50 | # 51 | # Hooks that are run before pushing changes 52 | # 53 | PrePush: 54 | RSpec: 55 | enabled: true 56 | -------------------------------------------------------------------------------- /app/api/v1/envelope_communities.rb: -------------------------------------------------------------------------------- 1 | require 'entities/envelope_community' 2 | require 'envelope_community' 3 | require 'helpers/shared_helpers' 4 | require 'policies/envelope_community_policy' 5 | 6 | module API 7 | module V1 8 | # Envelope communities 9 | class EnvelopeCommunities < Grape::API 10 | helpers CommunityHelpers 11 | helpers SharedHelpers 12 | 13 | resources :envelope_communities do 14 | before do 15 | authenticate! 16 | end 17 | 18 | desc 'Create an envelope community' 19 | params do 20 | requires :name, type: String, desc: 'Envelope community name' 21 | optional :default, type: Boolean 22 | optional :secured, type: Boolean 23 | optional :secured_search, type: Boolean 24 | end 25 | post do 26 | community = EnvelopeCommunity.find_or_initialize_by(name: params[:name]) 27 | authorize community, :create? 28 | community.update!(params.slice('default', 'secured', 'secured_search')) 29 | present community, with: API::Entities::EnvelopeCommunity 30 | status :created 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/jobs/precalculate_description_sets_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'precalculate_description_sets_job' 2 | 3 | RSpec.describe PrecalculateDescriptionSetsJob do 4 | describe '#perform' do 5 | let(:envelope) { create(:envelope) } 6 | 7 | context 'no envelope' do # rubocop:todo RSpec/ContextWording 8 | before do 9 | # rubocop:todo RSpec/MessageSpies 10 | # rubocop:todo RSpec/ExpectInHook 11 | expect(PrecalculateDescriptionSets).not_to receive(:process) 12 | # rubocop:enable RSpec/ExpectInHook 13 | # rubocop:enable RSpec/MessageSpies 14 | end 15 | 16 | it 'does nothing' do 17 | described_class.new.perform(0) 18 | end 19 | end 20 | 21 | context 'with envelope' do 22 | before do 23 | # rubocop:todo RSpec/MessageSpies 24 | # rubocop:todo RSpec/ExpectInHook 25 | expect(PrecalculateDescriptionSets).to receive(:process).with(envelope) 26 | # rubocop:enable RSpec/ExpectInHook 27 | # rubocop:enable RSpec/MessageSpies 28 | end 29 | 30 | it 'pre-calculates description sets' do 31 | described_class.new.perform(envelope.id) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/api/v1/indexed_resources.rb: -------------------------------------------------------------------------------- 1 | require 'helpers/community_helpers' 2 | require 'helpers/envelope_helpers' 3 | 4 | module API 5 | module V1 6 | # Indexed resources API endpoints 7 | class IndexedResources < MountableAPI 8 | mounted do 9 | helpers CommunityHelpers 10 | helpers SharedHelpers 11 | 12 | before do 13 | authenticate! 14 | end 15 | 16 | resources :indexed_resources do 17 | desc 'Retrieves the indexed resources schema' 18 | get 'schema' do 19 | IndexedEnvelopeResource.columns_hash 20 | end 21 | 22 | desc 'Retrives an indexed resources by its CTID' 23 | params do 24 | requires :ctid, type: String, desc: 'CTID' 25 | end 26 | get ':ctid' do 27 | EnvelopeCommunity 28 | .find_by!(name: select_community) 29 | .indexed_envelope_resources 30 | .find_by!('ceterms:ctid' => params[:ctid]) 31 | rescue ActiveRecord::RecordNotFound 32 | raise ActiveRecord::RecordNotFound.new("Couldn't find the indexed resource") 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /terraform/modules/vpc/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | description = "Environment name (e.g., dev, prod)" 3 | type = string 4 | } 5 | 6 | variable "project_name" { 7 | description = "Project name" 8 | type = string 9 | } 10 | 11 | variable "vpc_cidr" { 12 | description = "CIDR block for the VPC" 13 | type = string 14 | default = "10.19.0.0/16" 15 | } 16 | 17 | variable "public_subnet_cidrs" { 18 | description = "List of public subnet CIDR blocks" 19 | type = list(string) 20 | default = [] 21 | } 22 | 23 | variable "private_subnet_cidrs" { 24 | description = "List of private subnet CIDR blocks" 25 | type = list(string) 26 | default = [] 27 | } 28 | 29 | variable "single_nat_gateway" { 30 | description = "If true, create a single NAT Gateway shared by all private subnets; otherwise one per public subnet." 31 | type = bool 32 | default = true 33 | } 34 | 35 | variable "azs" { 36 | description = "Availability Zones" 37 | type = list(string) 38 | default = ["us-east-1a", "us-east-1b"] 39 | } 40 | 41 | variable "common_tags" { 42 | type = map(string) 43 | default = { 44 | "name" = "default value" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spec/api/v1/shared_examples/missing_envelope.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'missing envelope' do |verb| 2 | before do 3 | @params = defined?(params) ? params : {} 4 | end 5 | 6 | context 'with non-existent envelope' do 7 | before do 8 | public_send(verb, 9 | '/learning-registry/envelopes/non-existent-envelope-id', 10 | @params) # rubocop:todo RSpec/InstanceVariable 11 | end 12 | 13 | it { expect_status(:not_found) } 14 | 15 | it 'returns the list of validation errors' do 16 | expect_json_keys(:errors) 17 | expect_json('errors.0', 'Couldn\'t find Envelope') 18 | end 19 | end 20 | 21 | context 'with envelope in different metadata community' do 22 | let(:credential) { create(:envelope, :from_cer) } 23 | 24 | before do 25 | public_send(verb, 26 | "/learning-registry/envelopes/#{credential.envelope_id}", 27 | @params) # rubocop:todo RSpec/InstanceVariable 28 | end 29 | 30 | it { expect_status(:not_found) } 31 | 32 | it 'returns the list of validation errors' do 33 | expect_json_keys(:errors) 34 | expect_json('errors.0', 'Couldn\'t find Envelope') 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/api/v1/revisions_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe API::V1::Revisions do 2 | let!(:envelope) { create(:envelope, envelope_version: '0.9.0') } 3 | 4 | # rubocop:todo RSpec/ContextWording 5 | context 'GET /:community/envelopes/:envelope_id/revisions/:revision_id' do 6 | # rubocop:enable RSpec/ContextWording 7 | before do 8 | with_versioned_envelope(envelope) do 9 | get "/learning-registry/envelopes/#{id}" \ 10 | "/revisions/#{envelope.versions.first.id}" 11 | end 12 | end 13 | 14 | context 'by ID' do # rubocop:todo RSpec/ContextWording 15 | let(:id) { envelope.envelope_id } 16 | 17 | it { expect_status(:ok) } 18 | 19 | it 'retrieves the desired envelope' do 20 | expect_json(envelope_id: envelope.envelope_id) 21 | expect_json(envelope_version: '0.9.0') 22 | end 23 | end 24 | 25 | context 'by CTID' do # rubocop:todo RSpec/ContextWording 26 | let(:id) { envelope.envelope_ceterms_ctid } 27 | 28 | it { expect_status(:ok) } 29 | 30 | it 'retrieves the desired envelope' do 31 | expect_json(envelope_id: envelope.envelope_id) 32 | expect_json(envelope_version: '0.9.0') 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require File.expand_path('config/environment', __dir__) 2 | require 'rack/cors' 3 | require 'sidekiq/web' 4 | 5 | use Rack::Session::Cookie, key: 'rack.session', 6 | path: '/', 7 | secret: ENV.fetch('SECRET_KEY_BASE') 8 | 9 | use Rack::TryStatic, root: 'public', urls: %w[/], try: %w[.html index.html] 10 | 11 | use Rack::Cors do 12 | allow do 13 | origins '*' 14 | resource '*', headers: :any, methods: :get 15 | end 16 | end 17 | 18 | use Airbrake::Rack::Middleware if ENV['AIRBRAKE_PROJECT_ID'].present? 19 | 20 | map '/sidekiq' do 21 | unless MR.env == 'development' 22 | use Rack::Auth::Basic, 'Protected Area' do |username, password| 23 | username_matches = Rack::Utils.secure_compare( 24 | Digest::SHA256.hexdigest(username), 25 | Digest::SHA256.hexdigest(ENV.fetch('SIDEKIQ_USERNAME', nil)) 26 | ) 27 | 28 | password_matches = Rack::Utils.secure_compare( 29 | Digest::SHA256.hexdigest(password), 30 | Digest::SHA256.hexdigest(ENV.fetch('SIDEKIQ_PASSWORD', nil)) 31 | ) 32 | 33 | username_matches && password_matches 34 | end 35 | end 36 | 37 | run Sidekiq::Web 38 | end 39 | 40 | run API::Base 41 | -------------------------------------------------------------------------------- /app/models/organization.rb: -------------------------------------------------------------------------------- 1 | # Organization on whose behalf publishing is done 2 | class Organization < ActiveRecord::Base 3 | include AttributeNormalizer 4 | 5 | NOT_EMPTY = "Organization has published resources, can't be removed".freeze 6 | 7 | belongs_to :admin 8 | has_many :organization_publishers 9 | has_many :owned_envelopes, class_name: 'Envelope' 10 | has_many :published_envelopes, 11 | class_name: 'Envelope', 12 | dependent: :delete_all, 13 | foreign_key: :publishing_organization_id 14 | has_many :publishers, through: :organization_publishers 15 | 16 | validates :name, presence: true 17 | validates :admin, presence: true 18 | validate :ctid_format, if: :_ctid? 19 | 20 | normalize_attribute :name, with: :squish 21 | 22 | before_save :ensure_ctid 23 | 24 | before_destroy :remove_deleted_envelopes 25 | 26 | private 27 | 28 | def ctid_format 29 | return if _ctid.starts_with?('ce-') && UUID.validate(_ctid[3..(_ctid.size - 1)]) 30 | 31 | errors.add(:_ctid, :invalid) 32 | end 33 | 34 | def ensure_ctid 35 | self._ctid ||= "ce-#{SecureRandom.uuid}" 36 | end 37 | 38 | def remove_deleted_envelopes 39 | owned_envelopes.deleted.delete_all 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /.cloud66/deploy_hooks.yml: -------------------------------------------------------------------------------- 1 | production: &production 2 | after_checkout: 3 | - source: /.cloud66/install_postgresql_client.sh 4 | destination: /tmp/install_postgresql_client.sh 5 | target: rails 6 | execute: true 7 | sudo: true 8 | apply_during: build_only 9 | run_on: all_servers 10 | - source: /.cloud66/install_new_relic_agent.sh 11 | destination: /tmp/install_new_relic_agent.sh 12 | target: any 13 | execute: true 14 | sudo: true 15 | apply_during: build_only 16 | run_on: all_servers 17 | 18 | after_rails: 19 | - command: erb .cloud66/log_files.yml.erb > /etc/log_files.yml 20 | sudo: true 21 | target: rails 22 | apply_during: build_only 23 | 24 | - source: /.cloud66/remote_syslog.init.d 25 | destination: /etc/init.d/remote_syslog 26 | sudo: true 27 | target: rails 28 | apply_during: build_only 29 | 30 | - source: /.cloud66/papertrail.sh 31 | destination: /tmp/papertrail.sh 32 | target: rails 33 | sudo: true 34 | execute: true 35 | apply_during: build_only 36 | 37 | development: 38 | <<: *production 39 | 40 | sandbox: 41 | <<: *production 42 | 43 | staging: 44 | <<: *production 45 | -------------------------------------------------------------------------------- /app/models/registry_metadata.rb: -------------------------------------------------------------------------------- 1 | require 'digital_signature' 2 | require 'terms_of_service' 3 | require 'identity' 4 | 5 | # Virtual model that represents the fields inside an envelope 6 | # TODO keep adding attributes until complete (http://docs.learningregistry.org/en/latest/spec/Resource_Data_Data_Model/index.html#resource-data-description-data-model) 7 | class RegistryMetadata 8 | include Virtus.model 9 | include ActiveModel::Validations 10 | 11 | attribute :digital_signature, DigitalSignature 12 | attribute :keys, Array 13 | attribute :terms_of_service, TermsOfService 14 | attribute :payload_placement, String 15 | attribute :identity, Identity 16 | 17 | validates :payload_placement, presence: true, 18 | inclusion: { in: %w[inline linked attached] } 19 | 20 | validate do 21 | errors.add :digital_signature unless digital_signature.valid? 22 | errors.add :terms_of_service unless terms_of_service.valid? 23 | errors.add :identity unless identity.valid? 24 | end 25 | 26 | def initialize(attributes) 27 | self.digital_signature ||= DigitalSignature.new 28 | self.terms_of_service ||= TermsOfService.new 29 | self.identity ||= Identity.new 30 | 31 | super 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/api/v1/revisions.rb: -------------------------------------------------------------------------------- 1 | require 'envelope' 2 | require 'entities/envelope' 3 | require 'helpers/shared_helpers' 4 | 5 | module API 6 | module V1 7 | # Implements all the endpoints related to envelope revisions 8 | module Revisions 9 | # rubocop:todo Lint/MissingCopEnableDirective 10 | # rubocop:disable Metrics/MethodLength 11 | # rubocop:enable Lint/MissingCopEnableDirective 12 | def self.included(base) 13 | base.instance_eval do 14 | helpers SharedHelpers 15 | 16 | resource :revisions do 17 | route_param :revision_id, desc: 'The revision identifier' do 18 | desc 'Retrieves a specific envelope version', 19 | entity: API::Entities::Envelope 20 | params do 21 | use :envelope_community 22 | use :envelope_id 23 | requires :revision_id, desc: 'Unique revision identifier' 24 | end 25 | get do 26 | envelope = @envelope.versions.find(params[:revision_id]).reify 27 | 28 | present envelope, with: API::Entities::Envelope, is_version: true 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/models/indexed_envelope_resource.rb: -------------------------------------------------------------------------------- 1 | require 'indexed_envelope_resource_reference' 2 | 3 | # A flattened out version of an envelope resource's payload 4 | class IndexedEnvelopeResource < ActiveRecord::Base 5 | enum :publication_status, MR.envelope_publication_statuses 6 | 7 | belongs_to :envelope_community 8 | belongs_to :envelope_resource 9 | has_one :envelope, through: :envelope_resource 10 | has_many :references, 11 | class_name: 'IndexedEnvelopeResourceReference', 12 | foreign_key: :resource_id 13 | 14 | before_save :assign_metadata_attributes 15 | 16 | def self.schema_columns_hash 17 | columns_hash 18 | end 19 | 20 | def assign_metadata_attributes # rubocop:todo Metrics/AbcSize 21 | self.envelope_community = envelope.envelope_community 22 | self.public_record = !envelope_community.secured? 23 | self.publication_status = envelope.publication_status 24 | 25 | self['search:recordCreated'] = envelope.created_at 26 | self['search:recordOwnedBy'] = envelope.organization&._ctid 27 | self['search:recordPublishedBy'] = envelope.publishing_organization&._ctid 28 | self['search:resourcePublishType'] = envelope.resource_publish_type 29 | self['search:recordUpdated'] = envelope.updated_at 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/api/entities/node_headers.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module Entities 3 | # Presenter for the node headers added automatically 4 | class NodeHeaders < Grape::Entity 5 | expose :resource_digest 6 | expose :versions, 7 | as: :revision_history, 8 | unless: :is_version, 9 | using: API::Entities::Version, 10 | documentation: { is_array: true, 11 | desc: 'Revision history of the envelope' } 12 | expose :created_at, 13 | documentation: { type: 'dateTime', 14 | desc: 'Creation date' } 15 | expose :updated_at, 16 | documentation: { type: 'dateTime', 17 | desc: 'Last modification date' } 18 | expose :deleted_at, 19 | documentation: { type: 'dateTime', 20 | desc: 'Deletion date' } 21 | 22 | def created_at 23 | Time.zone.parse(object.created_at) if object.created_at? 24 | end 25 | 26 | def deleted_at 27 | Time.zone.parse(object.deleted_at) if object.deleted_at? 28 | end 29 | 30 | def updated_at 31 | Time.zone.parse(object.updated_at) if object.updated_at? 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /terraform/environments/eks/k8s-manifests-sandbox/external-secrets-values.yaml: -------------------------------------------------------------------------------- 1 | # Helm values for External Secrets Operator (ESO) 2 | # ============================================== 3 | # These values are consumed by the official Helm chart hosted at 4 | # https://charts.external-secrets.io 5 | # 6 | # The file is parameterised with two placeholders that **must** be 7 | # substituted before you run `helm upgrade --install`: 8 | # 9 | # – the IAM Role ARN that Terraform 10 | # outputs as `external_secrets_irsa_role_arn`. 11 | # – the AWS region where your cluster and 12 | # Secrets Manager live (e.g. us-east-1). 13 | # 14 | # Either replace those strings manually or use `envsubst` as shown in 15 | # the README. 16 | 17 | installCRDs: true 18 | 19 | serviceAccount: 20 | name: external-secrets 21 | annotations: 22 | eks.amazonaws.com/role-arn: arn:aws:iam::996810415034:role/ce-registry-eks-external-secrets-irsa-role 23 | 24 | env: 25 | AWS_REGION: us-east-1 26 | AWS_DEFAULT_REGION: us-east-1 27 | 28 | # Default resource requests/limits are conservative but can be tuned. 29 | resources: 30 | requests: 31 | cpu: 100m 32 | memory: 128Mi 33 | limits: 34 | cpu: 200m 35 | memory: 256Mi 36 | --------------------------------------------------------------------------------