clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
langcode: en
status: true
dependencies:
module:
- system
id: action--action
disabled: true
path: action/action
resourceType: action--action
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
type:
fieldName: type
publicName: type
enhancer:
id: ''
disabled: false
plugin:
fieldName: plugin
publicName: plugin
enhancer:
id: ''
disabled: false
configuration:
fieldName: configuration
publicName: configuration
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,116 @@
langcode: en
status: true
dependencies: { }
id: base_field_override--base_field_override
disabled: true
path: base_field_override/base_field_override
resourceType: base_field_override--base_field_override
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
field_name:
fieldName: field_name
publicName: field_name
enhancer:
id: ''
disabled: false
entity_type:
fieldName: entity_type
publicName: entity_type
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
required:
fieldName: required
publicName: required
enhancer:
id: ''
disabled: false
translatable:
fieldName: translatable
publicName: translatable
enhancer:
id: ''
disabled: false
default_value:
fieldName: default_value
publicName: default_value
enhancer:
id: ''
disabled: false
default_value_callback:
fieldName: default_value_callback
publicName: default_value_callback
enhancer:
id: ''
disabled: false
settings:
fieldName: settings
publicName: settings
enhancer:
id: ''
disabled: false
field_type:
fieldName: field_type
publicName: field_type
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,94 @@
langcode: en
status: true
dependencies:
module:
- block
id: block--block
disabled: true
path: block/block
resourceType: block--block
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
theme:
fieldName: theme
publicName: theme
enhancer:
id: ''
disabled: false
region:
fieldName: region
publicName: region
enhancer:
id: ''
disabled: false
weight:
fieldName: weight
publicName: weight
enhancer:
id: ''
disabled: false
provider:
fieldName: provider
publicName: provider
enhancer:
id: ''
disabled: false
plugin:
fieldName: plugin
publicName: plugin
enhancer:
id: ''
disabled: false
settings:
fieldName: settings
publicName: settings
enhancer:
id: ''
disabled: false
visibility:
fieldName: visibility
publicName: visibility
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,130 @@
langcode: en
status: true
dependencies:
module:
- consumers
id: consumer--consumer
disabled: true
path: consumer/consumer
resourceType: consumer--consumer
resourceFields:
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
owner_id:
fieldName: owner_id
publicName: owner_id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
image:
fieldName: image
publicName: image
enhancer:
id: ''
disabled: false
third_party:
fieldName: third_party
publicName: third_party
enhancer:
id: ''
disabled: false
is_default:
fieldName: is_default
publicName: is_default
enhancer:
id: ''
disabled: false
default_langcode:
fieldName: default_langcode
publicName: default_langcode
enhancer:
id: ''
disabled: false
secret:
fieldName: secret
publicName: secret
enhancer:
id: ''
disabled: false
confidential:
fieldName: confidential
publicName: confidential
enhancer:
id: ''
disabled: false
roles:
fieldName: roles
publicName: roles
enhancer:
id: ''
disabled: false
redirect:
fieldName: redirect
publicName: redirect
enhancer:
id: ''
disabled: false
user_id:
fieldName: user_id
publicName: user_id
enhancer:
id: ''
disabled: false
pkce:
fieldName: pkce
publicName: pkce
enhancer:
id: ''
disabled: false
client_id:
fieldName: client_id
publicName: client_id
enhancer:
id: ''
disabled: false
grant_user_access:
fieldName: grant_user_access
publicName: grant_user_access
enhancer:
id: ''
disabled: false
limit_requested_access:
fieldName: limit_requested_access
publicName: limit_requested_access
enhancer:
id: ''
disabled: false
limit_user_access:
fieldName: limit_user_access
publicName: limit_user_access
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,68 @@
langcode: en
status: true
dependencies: { }
id: date_format--date_format
disabled: true
path: date_format/date_format
resourceType: date_format--date_format
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
locked:
fieldName: locked
publicName: locked
enhancer:
id: ''
disabled: false
pattern:
fieldName: pattern
publicName: pattern
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,80 @@
langcode: en
status: true
dependencies: { }
id: entity_form_display--entity_form_display
disabled: true
path: entity_form_display/entity_form_display
resourceType: entity_form_display--entity_form_display
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
targetEntityType:
fieldName: targetEntityType
publicName: targetEntityType
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
mode:
fieldName: mode
publicName: mode
enhancer:
id: ''
disabled: false
content:
fieldName: content
publicName: content
enhancer:
id: ''
disabled: false
hidden:
fieldName: hidden
publicName: hidden
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,68 @@
langcode: en
status: true
dependencies: { }
id: entity_form_mode--entity_form_mode
disabled: true
path: entity_form_mode/entity_form_mode
resourceType: entity_form_mode--entity_form_mode
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
targetEntityType:
fieldName: targetEntityType
publicName: targetEntityType
enhancer:
id: ''
disabled: false
cache:
fieldName: cache
publicName: cache
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,80 @@
langcode: en
status: true
dependencies: { }
id: entity_view_display--entity_view_display
disabled: true
path: entity_view_display/entity_view_display
resourceType: entity_view_display--entity_view_display
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
targetEntityType:
fieldName: targetEntityType
publicName: targetEntityType
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
mode:
fieldName: mode
publicName: mode
enhancer:
id: ''
disabled: false
content:
fieldName: content
publicName: content
enhancer:
id: ''
disabled: false
hidden:
fieldName: hidden
publicName: hidden
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,68 @@
langcode: en
status: true
dependencies: { }
id: entity_view_mode--entity_view_mode
disabled: true
path: entity_view_mode/entity_view_mode
resourceType: entity_view_mode--entity_view_mode
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
targetEntityType:
fieldName: targetEntityType
publicName: targetEntityType
enhancer:
id: ''
disabled: false
cache:
fieldName: cache
publicName: cache
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,118 @@
langcode: en
status: true
dependencies:
module:
- field
id: field_config--field_config
disabled: true
path: field_config/field_config
resourceType: field_config--field_config
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
field_name:
fieldName: field_name
publicName: field_name
enhancer:
id: ''
disabled: false
entity_type:
fieldName: entity_type
publicName: entity_type
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
required:
fieldName: required
publicName: required
enhancer:
id: ''
disabled: false
translatable:
fieldName: translatable
publicName: translatable
enhancer:
id: ''
disabled: false
default_value:
fieldName: default_value
publicName: default_value
enhancer:
id: ''
disabled: false
default_value_callback:
fieldName: default_value_callback
publicName: default_value_callback
enhancer:
id: ''
disabled: false
settings:
fieldName: settings
publicName: settings
enhancer:
id: ''
disabled: false
field_type:
fieldName: field_type
publicName: field_type
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,118 @@
langcode: en
status: true
dependencies:
module:
- field
id: field_storage_config--field_storage_config
disabled: true
path: field_storage_config/field_storage_config
resourceType: field_storage_config--field_storage_config
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
field_name:
fieldName: field_name
publicName: field_name
enhancer:
id: ''
disabled: false
entity_type:
fieldName: entity_type
publicName: entity_type
enhancer:
id: ''
disabled: false
type:
fieldName: type
publicName: type
enhancer:
id: ''
disabled: false
settings:
fieldName: settings
publicName: settings
enhancer:
id: ''
disabled: false
module:
fieldName: module
publicName: module
enhancer:
id: ''
disabled: false
locked:
fieldName: locked
publicName: locked
enhancer:
id: ''
disabled: false
cardinality:
fieldName: cardinality
publicName: cardinality
enhancer:
id: ''
disabled: false
translatable:
fieldName: translatable
publicName: translatable
enhancer:
id: ''
disabled: false
indexes:
fieldName: indexes
publicName: indexes
enhancer:
id: ''
disabled: false
persist_with_no_fields:
fieldName: persist_with_no_fields
publicName: persist_with_no_fields
enhancer:
id: ''
disabled: false
custom_storage:
fieldName: custom_storage
publicName: custom_storage
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,76 @@
langcode: en
status: true
dependencies:
module:
- filter
id: filter_format--filter_format
disabled: true
path: filter_format/filter_format
resourceType: filter_format--filter_format
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
name:
fieldName: name
publicName: name
enhancer:
id: ''
disabled: false
format:
fieldName: format
publicName: format
enhancer:
id: ''
disabled: false
weight:
fieldName: weight
publicName: weight
enhancer:
id: ''
disabled: false
roles:
fieldName: roles
publicName: roles
enhancer:
id: ''
disabled: false
filters:
fieldName: filters
publicName: filters
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,64 @@
langcode: en
status: true
dependencies:
module:
- image
id: image_style--image_style
disabled: true
path: image_style/image_style
resourceType: image_style--image_style
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
name:
fieldName: name
publicName: name
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
effects:
fieldName: effects
publicName: effects
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,74 @@
langcode: en
status: true
dependencies: { }
id: jsonapi_resource_config--jsonapi_resource_config
disabled: true
path: jsonapi_resource_config/jsonapi_resource_config
resourceType: jsonapi_resource_config--jsonapi_resource_config
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
disabled:
fieldName: disabled
publicName: disabled
enhancer:
id: ''
disabled: false
path:
fieldName: path
publicName: path
enhancer:
id: ''
disabled: false
resourceType:
fieldName: resourceType
publicName: resourceType
enhancer:
id: ''
disabled: false
resourceFields:
fieldName: resourceFields
publicName: resourceFields
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,70 @@
langcode: en
status: true
dependencies:
module:
- system
id: menu--menu
disabled: true
path: menu/menu
resourceType: menu--menu
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
locked:
fieldName: locked
publicName: locked
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,112 @@
langcode: en
status: true
dependencies:
module:
- migrate_plus
id: migration--migration
disabled: true
path: migration/migration
resourceType: migration--migration
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
class:
fieldName: class
publicName: class
enhancer:
id: ''
disabled: false
field_plugin_method:
fieldName: field_plugin_method
publicName: field_plugin_method
enhancer:
id: ''
disabled: false
cck_plugin_method:
fieldName: cck_plugin_method
publicName: cck_plugin_method
enhancer:
id: ''
disabled: false
migration_tags:
fieldName: migration_tags
publicName: migration_tags
enhancer:
id: ''
disabled: false
migration_group:
fieldName: migration_group
publicName: migration_group
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
source:
fieldName: source
publicName: source
enhancer:
id: ''
disabled: false
process:
fieldName: process
publicName: process
enhancer:
id: ''
disabled: false
destination:
fieldName: destination
publicName: destination
enhancer:
id: ''
disabled: false
migration_dependencies:
fieldName: migration_dependencies
publicName: migration_dependencies
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,82 @@
langcode: en
status: true
dependencies:
module:
- migrate_plus
id: migration_group--migration_group
disabled: true
path: migration_group/migration_group
resourceType: migration_group--migration_group
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
source_type:
fieldName: source_type
publicName: source_type
enhancer:
id: ''
disabled: false
module:
fieldName: module
publicName: module
enhancer:
id: ''
disabled: false
shared_configuration:
fieldName: shared_configuration
publicName: shared_configuration
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,76 @@
langcode: en
status: true
dependencies:
config:
- simple_oauth.oauth2_token.bundle.access_token
id: oauth2_token--access_token
disabled: true
path: oauth2_token/access_token
resourceType: oauth2_token--access_token
resourceFields:
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
auth_user_id:
fieldName: auth_user_id
publicName: auth_user_id
enhancer:
id: ''
disabled: false
client:
fieldName: client
publicName: client
enhancer:
id: ''
disabled: false
scopes:
fieldName: scopes
publicName: scopes
enhancer:
id: ''
disabled: false
value:
fieldName: value
publicName: value
enhancer:
id: ''
disabled: false
created:
fieldName: created
publicName: created
enhancer:
id: ''
disabled: false
changed:
fieldName: changed
publicName: changed
enhancer:
id: ''
disabled: false
expire:
fieldName: expire
publicName: expire
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,76 @@
langcode: en
status: true
dependencies:
config:
- simple_oauth.oauth2_token.bundle.auth_code
id: oauth2_token--auth_code
disabled: true
path: oauth2_token/auth_code
resourceType: oauth2_token--auth_code
resourceFields:
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
auth_user_id:
fieldName: auth_user_id
publicName: auth_user_id
enhancer:
id: ''
disabled: false
client:
fieldName: client
publicName: client
enhancer:
id: ''
disabled: false
scopes:
fieldName: scopes
publicName: scopes
enhancer:
id: ''
disabled: false
value:
fieldName: value
publicName: value
enhancer:
id: ''
disabled: false
created:
fieldName: created
publicName: created
enhancer:
id: ''
disabled: false
changed:
fieldName: changed
publicName: changed
enhancer:
id: ''
disabled: false
expire:
fieldName: expire
publicName: expire
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,76 @@
langcode: en
status: true
dependencies:
config:
- simple_oauth.oauth2_token.bundle.refresh_token
id: oauth2_token--refresh_token
disabled: true
path: oauth2_token/refresh_token
resourceType: oauth2_token--refresh_token
resourceFields:
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
bundle:
fieldName: bundle
publicName: bundle
enhancer:
id: ''
disabled: false
auth_user_id:
fieldName: auth_user_id
publicName: auth_user_id
enhancer:
id: ''
disabled: false
client:
fieldName: client
publicName: client
enhancer:
id: ''
disabled: false
scopes:
fieldName: scopes
publicName: scopes
enhancer:
id: ''
disabled: false
value:
fieldName: value
publicName: value
enhancer:
id: ''
disabled: false
created:
fieldName: created
publicName: created
enhancer:
id: ''
disabled: false
changed:
fieldName: changed
publicName: changed
enhancer:
id: ''
disabled: false
expire:
fieldName: expire
publicName: expire
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,70 @@
langcode: en
status: true
dependencies:
module:
- simple_oauth
id: oauth2_token_type--oauth2_token_type
disabled: true
path: oauth2_token_type/oauth2_token_type
resourceType: oauth2_token_type--oauth2_token_type
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
locked:
fieldName: locked
publicName: locked
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,58 @@
langcode: en
status: true
dependencies:
module:
- path_alias
id: path_alias--path_alias
disabled: true
path: path_alias/path_alias
resourceType: path_alias--path_alias
resourceFields:
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
revision_id:
fieldName: revision_id
publicName: revision_id
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
path:
fieldName: path
publicName: path
enhancer:
id: ''
disabled: false
alias:
fieldName: alias
publicName: alias
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
revision_default:
fieldName: revision_default
publicName: revision_default
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,70 @@
langcode: en
status: true
dependencies:
module:
- rest
id: rest_resource_config--rest_resource_config
disabled: true
path: rest_resource_config/rest_resource_config
resourceType: rest_resource_config--rest_resource_config
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
plugin_id:
fieldName: plugin_id
publicName: plugin_id
enhancer:
id: ''
disabled: false
granularity:
fieldName: granularity
publicName: granularity
enhancer:
id: ''
disabled: false
configuration:
fieldName: configuration
publicName: configuration
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,94 @@
langcode: en
status: true
dependencies:
module:
- views
id: view--view
disabled: true
path: view/view
resourceType: view--view
resourceFields:
uuid:
fieldName: uuid
publicName: uuid
enhancer:
id: ''
disabled: false
langcode:
fieldName: langcode
publicName: langcode
enhancer:
id: ''
disabled: false
status:
fieldName: status
publicName: status
enhancer:
id: ''
disabled: false
dependencies:
fieldName: dependencies
publicName: dependencies
enhancer:
id: ''
disabled: false
third_party_settings:
fieldName: third_party_settings
publicName: third_party_settings
enhancer:
id: ''
disabled: false
_core:
fieldName: _core
publicName: _core
enhancer:
id: ''
disabled: false
id:
fieldName: id
publicName: id
enhancer:
id: ''
disabled: false
label:
fieldName: label
publicName: label
enhancer:
id: ''
disabled: false
module:
fieldName: module
publicName: module
enhancer:
id: ''
disabled: false
description:
fieldName: description
publicName: description
enhancer:
id: ''
disabled: false
tag:
fieldName: tag
publicName: tag
enhancer:
id: ''
disabled: false
base_table:
fieldName: base_table
publicName: base_table
enhancer:
id: ''
disabled: false
base_field:
fieldName: base_field
publicName: base_field
enhancer:
id: ''
disabled: false
display:
fieldName: display
publicName: display
enhancer:
id: ''
disabled: false

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Hooks provided by farm_api.
*
* This file contains no working PHP code; it exists to provide additional
* documentation for doxygen as well as to document hooks in the standard
* Drupal manner.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter the a "meta.farm" data in the root /api endpoint.
*
* @param array &$data
* The data to be altered.
*/
function hook_farm_api_meta_alter(array &$data) {
// Add a custom key.
$data['mykey'] = 'myvalue';
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,13 @@
name: farmOS API
description: Provides configuration for the farmOS API.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- drupal:jsonapi
- entity:entity
- jsonapi_extras:jsonapi_extras
- jsonapi_schema:jsonapi_schema
- simple_oauth:simple_oauth
- simple_oauth:simple_oauth_static_scope
- subrequests:subrequests

View File

@@ -0,0 +1,147 @@
<?php
/**
* @file
* Install, update and uninstall functions for the farm_api module.
*/
use Drupal\Core\Utility\Error;
/**
* Implements hook_install().
*/
function farm_api_install() {
// Allow JSON:API write operations.
\Drupal::configFactory()->getEditable('jsonapi.settings')->set('read_only', FALSE)->save();
// Change the base JSON:API path to /api.
\Drupal::configFactory()->getEditable('jsonapi_extras.settings')->set('path_prefix', 'api')->save();
// Load the simple_oauth module settings.
$simple_oauth_settings = \Drupal::configFactory()->getEditable('simple_oauth.settings');
// Explicitly set the public/private key path.
$simple_oauth_settings->set('public_key', '../keys/public.key');
$simple_oauth_settings->set('private_key', '../keys/private.key');
// Use static scopes by default.
$simple_oauth_settings->set('scope_provider', 'static');
// Save simple_oauth settings.
$simple_oauth_settings->save();
// Generate key files for simple_oauth module.
// Code adapted from the contenta_jsonapi profile install tasks:
// https://github.com/contentacms/contenta_jsonapi/blob/8.x-3.x/contenta_jsonapi.profile
// Build all the dependencies manually to avoid having to rely on the
// container to be ready.
$dir_name = 'keys';
/** @var \Drupal\simple_oauth\Service\KeyGeneratorService $key_gen */
$key_gen = \Drupal::service('simple_oauth.key.generator');
/** @var \Drupal\simple_oauth\Service\Filesystem\FileSystemChecker $file_system_checker */
$file_system_checker = \Drupal::service('simple_oauth.filesystem_checker');
/** @var \Drupal\Core\File\FileSystem $file_system */
$file_system = \Drupal::service('file_system');
// Save keys in the "keys" directory outside of the webroot.
$relative_path = DRUPAL_ROOT . '/../' . $dir_name;
if (!$file_system_checker->isDirectory($relative_path)) {
$file_system->mkdir($relative_path);
}
$keys_path = $file_system->realpath($relative_path);
$pub_filename = sprintf('%s/public.key', $keys_path);
$pri_filename = sprintf('%s/private.key', $keys_path);
// Attempt to create keys if they don't exist.
if (!$file_system_checker->fileExist($pub_filename) || !$file_system_checker->fileExist($pri_filename)) {
try {
$key_gen->generateKeys($keys_path);
}
catch (\Exception $e) {
$logger = \Drupal::logger('farm_api');
Error::logException($logger, $e);
}
}
// Delete the "Default Consumer" created by the consumers module.
$default_consumer = \Drupal::entityTypeManager()->getStorage('consumer')
->loadByProperties(['label' => 'Default Consumer']);
if (!empty($default_consumer)) {
$default_consumer = reset($default_consumer);
$default_consumer->delete();
}
}
/**
* Implements hook_update_dependencies().
*/
function farm_api_update_dependencies() {
// The Consumers module added a client_id base field in version 1.14, which
// replaces our custom client_id field. We need to migrate the client IDs
// from our old field to the new field, which requires running our own
// update hooks before and after those of the Consumers module.
$dependencies['consumers'][8108] = [
'farm_api' => 9000,
];
$dependencies['farm_api'][9001] = [
'consumers' => 8109,
];
return $dependencies;
}
/**
* Save client_id values and uninstall farmOS client_id field.
*/
function farm_api_update_9000(&$sandbox) {
// This update hook must run before consumers_update_8108().
// @see farm_api_update_dependencies().
// Get all client_id values from the database.
$client_ids = [];
$result = \Drupal::database()->query('SELECT entity_id, client_id_value FROM {consumer__client_id}')->fetchAll();
foreach ($result as $record) {
$client_ids[$record->entity_id] = $record->client_id_value;
}
// Save the client_id values to Drupal state.
if (!empty($client_ids)) {
\Drupal::state()->set('farm_api_consumer_client_ids', $client_ids);
}
// Uninstall the client_id field.
/** @var \Drupal\entity\BundleFieldDefinition $definition */
$definition = \Drupal::service('farm_field.factory')->bundleFieldDefinition(['type' => 'string']);
$definition->setTargetEntityTypeId('consumer');
$definition->setName('client_id');
\Drupal::entityDefinitionUpdateManager()->uninstallFieldStorageDefinition($definition);
}
/**
* Restore client_id values saved from farmOS client_id field.
*/
function farm_api_update_9001(&$sandbox) {
// This update hook must run after consumers_update_8109().
// @see farm_api_update_dependencies().
// Load saved client_id values from old client_id field.
$client_ids = \Drupal::state()->get('farm_api_consumer_client_ids', []);
if (empty($client_ids)) {
return;
}
// Save client_id values to new client_id field.
// Use SQL update instead of entity save so tokens do not become invalidated.
$database = \Drupal::database();
foreach ($client_ids as $consumer_id => $client_id) {
$database->update('consumer_field_data')
->fields(['client_id' => $client_id])
->condition('id', $consumer_id)
->execute();
}
}

View File

@@ -0,0 +1,4 @@
farm_api:
default_permissions:
- grant simple_oauth codes
- issue subrequests

View File

@@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains farm_api.module.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\entity\BundleFieldDefinition;
/**
* Implements hook_consumers_list_alter().
*
* Display the client_id in the list of consumers.
*/
function farm_api_consumers_list_alter(&$data, $context) {
if ($context['type'] === 'header') {
$data['client_id'] = t('Client ID');
}
elseif ($context['type'] === 'row') {
$entity = $context['entity'];
$data['client_id'] = NULL;
if ($client_id = $entity->get('client_id')->value) {
$data['client_id'] = $client_id;
}
}
}
/**
* Implements hook_entity_base_field_info().
*/
function farm_api_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = [];
// Add allowed_origins field to the consumer entity.
if ($entity_type->id() == 'consumer') {
$fields['allowed_origins'] = BundleFieldDefinition::create('string')
->setLabel(t('Allowed origins'))
->setDescription(t('Configure CORS origins for this consumer.'))
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
->setDisplayOptions('form', [
'type' => 'string_textfield',
'settings' => [
'size' => 255,
'placeholder' => 'https://example.com',
],
]);
}
return $fields;
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* @file
* Post update functions for farm_settings module.
*/
/**
* Remove farm_api consumer bundle fields.
*/
function farm_api_post_update_remove_consumer_fields(&$sandbox = NULL) {
// Remove old consumer fields.
$fields = [
'grant_user_access',
'limit_user_access',
'limit_requested_access',
];
foreach ($fields as $field) {
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$roles_field_definition = $entity_definition_update_manager->getFieldStorageDefinition($field, 'consumer');
$entity_definition_update_manager->uninstallFieldStorageDefinition($roles_field_definition);
}
}
/**
* Enable static oauth2 scopes.
*/
function farm_api_post_update_enable_static_oauth2_scopes(&$sandbox = NULL) {
// Enable static scope module.
if (!\Drupal::service('module_handler')->moduleExists('simple_oauth_static_scope')) {
\Drupal::service('module_installer')->install(['simple_oauth_static_scope']);
}
// Use static scope provider.
$simple_oauth_settings = \Drupal::configFactory()->getEditable('simple_oauth.settings');
$simple_oauth_settings->set('scope_provider', 'static');
$simple_oauth_settings->save();
}
/**
* Enable default consumer module.
*/
function farm_api_post_update_enable_default_consumer_module(&$sandbox = NULL) {
// Check for an existing farm default consumer.
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
->loadByProperties(['client_id' => 'farm']);
if (!empty($consumers)) {
// Enable default consumer module.
if (!\Drupal::service('module_handler')->moduleExists('farm_api_default_consumer')) {
\Drupal::service('module_installer')->install(['farm_api_default_consumer']);
}
// Update values on the consumer.
/** @var \Drupal\consumers\Entity\ConsumerInterface $farm_default */
$farm_default = reset($consumers);
$farm_default->set('grant_types', ['authorization_code', 'refresh_token', 'password']);
$farm_default->save();
}
}

View File

@@ -0,0 +1,12 @@
services:
# Add CORS headers for allowed origins configured on consumers.
farm_api.cors_event_subscriber:
class: Drupal\farm_api\EventSubscriber\CorsResponseEventSubscriber
arguments: ['@entity_type.manager']
tags:
- { name: event_subscriber }
# Alter the root /api endpoint to include a meta.farm object.
farm_api.route_subscriber:
class: Drupal\farm_api\Routing\RouteSubscriber
tags:
- { name: event_subscriber }

View File

@@ -0,0 +1,8 @@
name: farmOS Default API Consumer
description: Provides a default consumer for using the farmOS API.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- farm:farm_api
- simple_oauth_password_grant:simple_oauth_password_grant

View File

@@ -0,0 +1,56 @@
<?php
/**
* @file
* Install and uninstall functions for the farm_api_default_consumer module.
*/
use Drupal\consumers\Entity\Consumer;
/**
* Implements hook_install().
*/
function farm_api_default_consumer_install() {
// Check for an existing farm default consumer.
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
->loadByProperties(['client_id' => 'farm']);
// If not found, create the farm default consumer.
if (empty($consumers)) {
$base_url = \Drupal::service('router.request_context')->getCompleteBaseUrl();
$farm_consumer = Consumer::create([
'label' => 'Farm default',
'client_id' => 'farm',
'access_token_expiration' => 3600,
'grant_types' => [
'authorization_code',
'refresh_token',
'password',
],
'redirect' => $base_url,
'is_default' => TRUE,
'secret' => NULL,
'confidential' => FALSE,
'third_party' => FALSE,
]);
$farm_consumer->save();
}
}
/**
* Implements hook_uninstall().
*/
function farm_api_default_consumer_uninstall() {
// Load the default farm consumer.
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
->loadByProperties(['client_id' => 'farm']);
// If found, delete the consumer.
if (!empty($consumers)) {
$farm_consumer = reset($consumers);
$farm_consumer->delete();
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Drupal\farm_api\Controller;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ProfileExtensionList;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\CacheableResourceResponse;
use Drupal\jsonapi\Controller\EntryPoint;
use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel;
use Drupal\jsonapi\JsonApiResource\NullIncludedData;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Extend the core jsonapi EntryPoint controller.
*
* Adds a "meta.farm" key to root /api endpoint.
*
* @ingroup farm
*
* @phpstan-ignore-next-line
*/
class FarmEntryPoint extends EntryPoint {
/**
* Farm profile info.
*
* @var mixed[]
*/
protected $farmProfileInfo;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* EntryPoint constructor.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $resource_type_repository
* The resource type repository.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
* @param \Drupal\Core\Extension\ProfileExtensionList $profile_extension_list
* The profile extension list service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ResourceTypeRepositoryInterface $resource_type_repository, AccountInterface $user, ProfileExtensionList $profile_extension_list, ModuleHandlerInterface $module_handler) {
parent::__construct($resource_type_repository, $user);
$this->farmProfileInfo = $profile_extension_list->getExtensionInfo('farm');
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('jsonapi.resource_type.repository'),
$container->get('current_user'),
$container->get('extension.list.profile'),
$container->get('module_handler'),
);
}
/**
* {@inheritdoc}
*/
public function index() {
// Get the base url.
global $base_url;
// Get normal response cache and data.
/** @var \Drupal\jsonapi\CacheableResourceResponse $response */
$response = parent::index();
$cacheability = $response->getCacheableMetadata();
$data = $response->getResponseData();
// Get urls and meta.
$urls = $data->getLinks();
$meta = $data->getMeta();
// Add a "farm" object to meta.
$meta['farm'] = [
'name' => $this->config('system.site')->get('name'),
'url' => $base_url,
'version' => $this->farmProfileInfo['version'],
];
// Allow modules to add additional meta information.
$this->moduleHandler->alter('farm_api_meta', $meta['farm']);
// Build a new response.
$new_response = new CacheableResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([]), new NullIncludedData(), $urls, $meta));
// Add the original response's cacheability.
$new_response->addCacheableDependency($cacheability);
return $new_response;
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Drupal\farm_api\EventSubscriber;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Responds to the Kernel Response event to add CORS headers.
*
* CORS headers are only added to requests from the allowed origins configured
* on consumer entities.
*/
class CorsResponseEventSubscriber implements EventSubscriberInterface {
/**
* Entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new CorsResponseEventSubscriber.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = ['addCorsHeaders'];
return $events;
}
/**
* Adds CORS headers to the response.
*
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The response event.
*/
public function addCorsHeaders(ResponseEvent $event) {
// Get the request headers.
$request = $event->getRequest();
$request_headers = $request->headers->all();
// Bail if the request has no origin header.
if (empty($request_headers['origin'])) {
return;
}
$request_origin = reset($request_headers['origin']);
// Load allowed_origins from all consumer entities.
$consumers = $this->entityTypeManager->getStorage('consumer')->loadMultiple();
$allowed_origins = array_reduce($consumers, function ($carry, $consumer) {
/** @var \Drupal\Core\Field\FieldItemListInterface $list */
$list = $consumer->get('allowed_origins');
$list_values = array_map(function ($list_item) {
return $list_item['value'] ? trim($list_item['value']) : NULL;
}, $list->getValue());
return array_merge($carry, $list_values);
}, []);
// Set the response headers if the request origin is allowed.
if (in_array($request_origin, $allowed_origins)) {
$response = $event->getResponse();
$response->headers->set('Access-Control-Allow-Origin', $request_origin, TRUE);
$response->headers->set('Access-Control-Allow-Credentials', 'true', TRUE);
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type,Content-Disposition,Authorization,X-CSRF-Token', TRUE);
$response->headers->set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,HEAD,OPTIONS', TRUE);
$response->headers->set('Vary', 'Origin', TRUE);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Drupal\farm_api\Routing;
use Drupal\Core\Routing\RouteObjectInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\farm_api\Controller\FarmEntryPoint;
use Symfony\Component\Routing\RouteCollection;
/**
* Alter routes for the farm_api module.
*
* @ingroup farm
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
public function alterRoutes(RouteCollection $collection) {
// Alter the root jsonapi.resource_list route to use the FarmEntryPoint
// controller. This allows us to add the meta.farm object.
if ($route = $collection->get('jsonapi.resource_list')) {
$route->setDefaults([RouteObjectInterface::CONTROLLER_NAME => FarmEntryPoint::class . '::index']);
}
}
}

View File

@@ -0,0 +1,7 @@
langcode: en
status: true
id: test
label: Test
description: ''
workflow: asset_default
new_revision: true

View File

@@ -0,0 +1,8 @@
langcode: en
status: true
id: test
label: Test
description: ''
name_pattern: 'Test log [log:id]'
workflow: log_default
new_revision: true

View File

@@ -0,0 +1,9 @@
name: farmOS API Tests
description: 'Support module for farmOS API testing.'
type: module
package: Testing
core_version_requirement: ^10
dependencies:
- farm:asset
- log:log
- simple_oauth_password_grant:simple_oauth_password_grant

View File

@@ -0,0 +1,12 @@
test:password:
description: 'Static scope for password grant.'
umbrella: false
grant_types:
authorization_code:
status: true
description: 'Authorization code grant.'
password:
status: true
description: 'Password grant'
granularity: 'permission'
permission: 'access content'

View File

@@ -0,0 +1,17 @@
<?php
namespace Drupal\farm_api_test\Plugin\Asset\AssetType;
use Drupal\farm_entity\Plugin\Asset\AssetType\FarmAssetType;
/**
* Provides the test asset type.
*
* @AssetType(
* id = "test",
* label = @Translation("Test"),
* )
*/
class Test extends FarmAssetType {
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Drupal\farm_api_test\Plugin\Log\LogType;
use Drupal\farm_entity\Plugin\Log\LogType\FarmLogType;
/**
* Provides the test log type.
*
* @LogType(
* id = "test",
* label = @Translation("Test"),
* )
*/
class Test extends FarmLogType {
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Drupal\Tests\farm_api\Functional;
use Drupal\Core\Url;
use Drupal\Tests\farm_test\Functional\FarmBrowserTestBase;
use Drupal\Tests\jsonapi\Functional\JsonApiRequestTestTrait;
use Drupal\consumers\Entity\Consumer;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\ResponseInterface;
/**
* Tests that CORS headers are properly added.
*
* @group farm
*/
class CorsResponseEventSubscriberTest extends FarmBrowserTestBase {
use JsonApiRequestTestTrait;
/**
* Test consumer entity.
*
* @var \Drupal\consumers\Entity\Consumer
*/
protected $consumer;
/**
* {@inheritdoc}
*/
protected static $modules = [
'farm_api',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a consumer for testing.
$this->consumer = Consumer::create([
'label' => $this->getRandomGenerator()->name(),
]);
$this->consumer->save();
}
/**
* Test CORS response headers are correctly added.
*/
public function testCorsResponseHeaders() {
// A request with no Origin should not have CORS headers on the response.
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$uri = "base://api";
$response = $this->request('OPTIONS', Url::fromUri($uri), $request_options);
$this->assertSame(200, $response->getStatusCode());
$this->assertValidCorsHeaders($response);
// Try an invalid origin.
$farmos_app_origin = 'https://farmOS.app';
$request_options[RequestOptions::HEADERS]['Origin'] = $farmos_app_origin;
$response = $this->request('OPTIONS', Url::fromUri($uri), $request_options);
$this->assertSame(200, $response->getStatusCode());
$this->assertValidCorsHeaders($response);
// Configure an allowed origin on the consumer.
$this->consumer->set('allowed_origins', [$farmos_app_origin]);
$this->consumer->save();
// Make a request with the allowed origin configured.
$response = $this->request('OPTIONS', Url::fromUri($uri), $request_options);
$this->assertSame(200, $response->getStatusCode());
$this->assertValidCorsHeaders($response, $farmos_app_origin);
// Add another allowed_origin and test that multiple allowed origins work.
$custom_app_origin = 'https://customApp.com';
$this->consumer->set('allowed_origins', [$farmos_app_origin, $custom_app_origin]);
$this->consumer->save();
// Make a request from the first allowed origin.
$request_options[RequestOptions::HEADERS]['Origin'] = $farmos_app_origin;
$response = $this->request('OPTIONS', Url::fromUri($uri), $request_options);
$this->assertSame(200, $response->getStatusCode());
$this->assertValidCorsHeaders($response, $farmos_app_origin);
// Make a request from the second allowed origin.
$request_options[RequestOptions::HEADERS]['Origin'] = $custom_app_origin;
$response = $this->request('OPTIONS', Url::fromUri($uri), $request_options);
$this->assertSame(200, $response->getStatusCode());
$this->assertValidCorsHeaders($response, $custom_app_origin);
}
/**
* Helper method to test valid CORS headers.
*
* @param \Psr\Http\Message\ResponseInterface $response
* The response to check.
* @param string|null $origin
* An optional origin to check. If NULL, then the request should have no
* CORS headers.
*/
protected function assertValidCorsHeaders(ResponseInterface $response, ?string $origin = NULL) {
// Cors headers to test.
$cors_headers = [
'Access-Control-Allow-Origin' => $origin,
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Headers' => 'Content-Type,Content-Disposition,Authorization,X-CSRF-Token',
'Access-Control-Allow-Methods' => 'GET,POST,PUT,DELETE,HEAD,OPTIONS',
];
// Check if the response should contain headers.
$needs_cors = !empty($origin);
foreach ($cors_headers as $header => $value) {
$this->assertEquals($needs_cors, $response->hasHeader($header), 'Response has correct CORS headers.');
if ($needs_cors) {
$this->assertEquals($value, $response->getHeader($header)[0], 'Response has correct header value.');
}
}
// Confirm that the "Vary" header contains "Origin" when CORS is in use.
if ($needs_cors) {
$this->assertTrue(str_contains($response->getHeader('Vary')[0], 'Origin'), 'Response Vary header contains Origin.');
}
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Drupal\Tests\farm_api\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\Tests\farm_test\Functional\FarmBrowserTestBase;
use Drupal\Tests\jsonapi\Functional\JsonApiRequestTestTrait;
use GuzzleHttp\RequestOptions;
/**
* Tests the API entry point functionality.
*
* @group farm
*/
class EntryPointTest extends FarmBrowserTestBase {
use JsonApiRequestTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'basic_auth',
'node',
'farm_api',
'views',
];
/**
* Run all tests.
*/
public function testAll() {
$this->doTestEntryPoint();
$this->doTestFarmMeta();
}
/**
* Test GET to the entry point.
*
* We extend the core JSONAPI EntryPoint controller to include a 'meta' key
* in the root controller. We need to test that we aren't changing anything
* else.
*
* This test is a copy of the core JSONAPI testEntryPoint test with the
* following modifications:
* - The base path is /api, not /jsonapi
* - The root document MUST have a 'meta' key.
*
* @see \Drupal\Tests\jsonapi\Functional\EntryPointTest
*/
public function doTestEntryPoint() {
$request_options = [];
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$response = $this->request('GET', Url::fromUri('base://api'), $request_options);
$document = Json::decode((string) $response->getBody());
$expected_cache_contexts = [
'url.query_args',
'url.site',
'user.roles:authenticated',
];
$this->assertTrue($response->hasHeader('X-Drupal-Cache-Contexts'));
$optimized_expected_cache_contexts = \Drupal::service('cache_contexts_manager')->optimizeTokens($expected_cache_contexts);
$this->assertSame($optimized_expected_cache_contexts, explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0]));
$links = $document['links'];
$this->assertMatchesRegularExpression('/.*\/api/', $links['self']['href']);
$this->assertMatchesRegularExpression('/.*\/api\/user\/user/', $links['user--user']['href']);
$this->assertMatchesRegularExpression('/.*\/api\/node_type\/node_type/', $links['node_type--node_type']['href']);
// farm_api alters the root document to include a 'meta' key.
$this->assertArrayHasKey('meta', $document);
// A `me` link must be present for authenticated users.
$user = $this->createUser();
$request_options[RequestOptions::HEADERS]['Authorization'] = 'Basic ' . base64_encode($user->name->value . ':' . $user->passRaw);
$response = $this->request('GET', Url::fromUri('base://api'), $request_options);
$document = Json::decode((string) $response->getBody());
$this->assertArrayHasKey('meta', $document);
$this->assertStringEndsWith('/api/user/user/' . $user->uuid(), $document['meta']['links']['me']['href']);
}
/**
* Test that the meta.farm data is correct.
*/
public function doTestFarmMeta() {
$request_options = [];
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$response = $this->request('GET', Url::fromUri('base://api'), $request_options);
$document = Json::decode((string) $response->getBody());
// Assert that the meta key exists.
$this->assertArrayHasKey('meta', $document);
// Assert that the meta.farm key exists.
$this->assertArrayHasKey('farm', $document['meta']);
// Get the farm profile info.
$farm_info = \Drupal::service('extension.list.profile')->getExtensionInfo('farm');
// Array of expected values.
$expected_values = [
'name' => $this->config('system.site')->get('name'),
'url' => $this->baseUrl,
'version' => $farm_info['version'],
];
foreach ($expected_values as $key => $value) {
$this->assertArrayHasKey($key, $document['meta']['farm']);
$this->assertEquals($value, $document['meta']['farm'][$key]);
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Drupal\Tests\farm_api\Functional;
use Drupal\Tests\farm_test\Functional\FarmBrowserTestBase;
/**
* Tests hook_install functionality of farm_api module.
*
* This must be implemented as a BrowserTest so that the module is fully
* installed while testing. The KernelTestBase does not fully install modules.
*
* @group farm
*/
class InstallTest extends FarmBrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'simple_oauth',
'farm_api',
// @todo Investigate why Views is needed.
'views',
];
/**
* Tests keys were created.
*/
public function testKeys() {
// Key directory.
$dir_name = 'keys';
// Save keys in the "keys" directory outside of the webroot.
$relative_path = DRUPAL_ROOT . '/../' . $dir_name;
$this->assertDirectoryExists($relative_path);
$pub_filename = sprintf('%s/public.key', $relative_path);
$pri_filename = sprintf('%s/private.key', $relative_path);
$this->assertFileExists($pub_filename);
$this->assertFileExists($pri_filename);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Drupal\Tests\farm_api\Functional;
use Drupal\Tests\simple_oauth\Functional\TokenBearerFunctionalTestBase;
/**
* Base class that handles common logic for OAuth tests.
*
* @group farm
*/
class OauthTestBase extends TokenBearerFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $profile = 'farm';
/**
* {@inheritdoc}
*/
protected static $modules = [
'image',
'node',
'serialization',
'simple_oauth',
'text',
'user',
'farm_api',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Add a client_id to the client.
$this->client->set('client_id', 'farm_test');
$this->client->set('confidential', FALSE);
$this->client->save();
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace Drupal\Tests\farm_api\Kernel;
use Drupal\Component\Serialization\Json;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Tests farmOS API features.
*
* @group farm
*/
class FarmApiTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected $profile = 'farm';
/**
* {@inheritdoc}
*/
protected static $modules = [
'asset',
'consumers',
'entity',
'farm_api',
'farm_api_test',
'farm_entity',
'farm_entity_views',
'farm_field',
'farm_log_asset',
'farm_role',
'farm_role_roles',
'file',
'image',
'jsonapi',
'jsonapi_extras',
'log',
'options',
'serialization',
'simple_oauth',
'state_machine',
'system',
'user',
'views',
];
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->installEntitySchema('consumer');
$this->installEntitySchema('asset');
$this->installEntitySchema('log');
$this->installConfig([
'farm_api_test',
'farm_entity_views',
'farm_role_roles',
'jsonapi',
'jsonapi_extras',
'system',
]);
// Set the install profile.
// This is necessary because farm_api's FarmEntryPoint needs to load the
// farm profile information from Drupal's extension list service. During
// kernel tests the installProfile property of the ExtensionList class does
// not get set automatically.
$this->setInstallProfile('farm');
// Set the site name so that we can check for it in /api meta.farm info.
\Drupal::configFactory()->getEditable('system.site')->set('name', 'API Test')->save();
// Allow JSON:API write operations and change the base path to /api.
// These would normally be done by farm_api_install(), which does not run
// in Kernel tests (it also does other things we don't need).
\Drupal::configFactory()->getEditable('jsonapi.settings')->set('read_only', FALSE)->save();
\Drupal::configFactory()->getEditable('jsonapi_extras.settings')->set('path_prefix', 'api')->save();
// Set up a user with the farm_manager role.
$user = $this->setUpCurrentUser([], [], FALSE);
$user->addRole('farm_manager');
}
/**
* Test common farmOS API requests.
*/
public function testApi() {
// Test that the API root path is /api and it contains meta.farm info.
$data = $this->apiRequest('/api');
$this->assertNotEmpty($data['meta']['farm']);
$this->assertEquals('API Test', $data['meta']['farm']['name']);
// Test creating an asset.
$asset_type = 'asset--test';
$payload = [
'type' => $asset_type,
'attributes' => [
'name' => 'Test asset',
],
];
$data = $this->apiRequest('/api/asset/test', 'POST', $payload);
$this->assertNotEmpty($data['data']['id']);
$this->assertEquals($asset_type, $data['data']['type']);
$this->assertEquals($payload['attributes']['name'], $data['data']['attributes']['name']);
// Get the asset ID.
$asset_id = $data['data']['id'];
// Test creating a log that references the asset.
$log_type = 'log--test';
$payload = [
'type' => $log_type,
'relationships' => [
'asset' => [
'data' => [
[
'id' => $asset_id,
'type' => $asset_type,
],
],
],
],
];
$data = $this->apiRequest('/api/log/test', 'POST', $payload);
$this->assertNotEmpty($data['data']['id']);
$this->assertEquals($log_type, $data['data']['type']);
$this->assertEquals($asset_id, $data['data']['relationships']['asset']['data'][0]['id']);
// Get the log ID.
$log_id = $data['data']['id'];
// Test that the asset and log appear in collection endpoints.
$data = $this->apiRequest('/api/asset/test');
$this->assertCount(1, $data['data']);
$this->assertEquals($asset_id, $data['data'][0]['id']);
$data = $this->apiRequest('/api/log/test');
$this->assertCount(1, $data['data']);
$this->assertEquals($log_id, $data['data'][0]['id']);
// Test retrieving both asset and log individually by UUID.
$data = $this->apiRequest('/api/asset/test/' . $asset_id);
$this->assertEquals($asset_id, $data['data']['id']);
$data = $this->apiRequest('/api/log/test/' . $log_id);
$this->assertEquals($log_id, $data['data']['id']);
// Test updating assets and logs.
$payload = [
'type' => $asset_type,
'id' => $asset_id,
'attributes' => [
'name' => 'Updated asset name',
],
];
$data = $this->apiRequest('/api/asset/test/' . $asset_id, 'PATCH', $payload);
$this->assertEquals($asset_id, $data['data']['id']);
$data = $this->apiRequest('/api/asset/test/' . $asset_id);
$this->assertEquals($payload['attributes']['name'], $data['data']['attributes']['name']);
$payload = [
'type' => $log_type,
'id' => $log_id,
'attributes' => [
'name' => 'Updated log name',
],
];
$data = $this->apiRequest('/api/log/test/' . $log_id, 'PATCH', $payload);
$this->assertEquals($log_id, $data['data']['id']);
$data = $this->apiRequest('/api/log/test/' . $log_id);
$this->assertEquals($payload['attributes']['name'], $data['data']['attributes']['name']);
// Test deleting logs and assets.
$this->apiRequest('/api/log/test/' . $log_id, 'DELETE');
$data = $this->apiRequest('/api/log/test');
$this->assertCount(0, $data['data']);
$data = $this->apiRequest('/api/asset/test/' . $asset_id, 'DELETE');
$data = $this->apiRequest('/api/asset/test');
$this->assertCount(0, $data['data']);
}
/**
* Helper function for performing an API request.
*
* @param string $endpoint
* The API endpoint.
* @param string $method
* The request method (eg: GET, POST, PATCH, DELETE).
* @param array $payload
* Array of data to send as a payload.
*
* @return array
* An array of JSON-decoded data returned by the request.
*/
protected function apiRequest(string $endpoint, string $method = 'GET', array $payload = []) {
$http_kernel = $this->container->get('http_kernel');
$content = '';
if (!empty($payload)) {
$content = Json::encode([
'data' => $payload,
]);
}
$request = Request::create($endpoint, $method, [], [], [], [], $content);
$request->headers->set('Accept', 'application/vnd.api+json');
$request->headers->set('Content-Type', 'application/vnd.api+json');
$response = $http_kernel->handle($request);
$expected_response = [
'GET' => Response::HTTP_OK,
'POST' => Response::HTTP_CREATED,
'PATCH' => Response::HTTP_OK,
'DELETE' => Response::HTTP_NO_CONTENT,
];
$this->assertEquals($expected_response[$method], $response->getStatusCode());
return Json::decode($response->getContent());
}
}