Skip to content

Commit

Permalink
Merge pull request #37 from mxr576/callback-url
Browse files Browse the repository at this point in the history
Add validation to app callback urls
  • Loading branch information
mxr576 authored Jul 3, 2018
2 parents 8047214 + 9adb6b9 commit 2263719
Show file tree
Hide file tree
Showing 18 changed files with 719 additions and 351 deletions.
12 changes: 11 additions & 1 deletion apigee_edge.module
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ function apigee_edge_entity_extra_field_info() {
return $extra;
}

/**
* Implements hook_field_formatter_info_alter().
*/
function apigee_edge_field_formatter_info_alter(array &$info) {
// Allows using the 'uri_link' formatter for the 'app_callback_url'
// field type, which uses it as default formatter.
// @see \Drupal\apigee_edge\Plugin\Field\FieldType\AppCallbackUrlItem
$info['uri_link']['field_types'][] = 'app_callback_url';
}

/**
* Implements hook_entity_view().
*/
Expand Down Expand Up @@ -270,7 +280,7 @@ function apigee_edge_form_alter(&$form, FormStateInterface $form_state, $form_id
* Form state.
*/
function _apigee_edge_entity_form_display_edit_form_validate(array &$form, FormStateInterface $form_state) {
$required = \Drupal::config('apigee_edge.common_app_settings')->get('required_base_fields');
$required = \Drupal::config('apigee_edge.developer_app_settings')->get('required_base_fields');

foreach ($form_state->getValue('fields') as $field_name => $data) {
if (in_array($field_name, $required) && $data['region'] === 'hidden') {
Expand Down
4 changes: 0 additions & 4 deletions config/install/apigee_edge.common_app_settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,5 @@ langcode: en
display_as_select: false
user_select: true
multiple_products: true
locked_base_fields:
- displayName
required_base_fields:
- displayName
default_products: []
analytics_environment: prod
8 changes: 8 additions & 0 deletions config/install/apigee_edge.developer_app_settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ entity_label_singular: ''
entity_label_plural: ''
cache_expiration: 900
credential_lifetime: 0
locked_base_fields:
- displayName
required_base_fields:
- displayName
callback_url_pattern: '^https?:\/\/.*$'
callback_url_pattern_error_message: 'The Callback URL must start with http:// or https://'
callback_url_description: 'External site to which a consumer of this app is redirected to login when using three-legged OAuth.'
callback_url_placeholder: ''
27 changes: 19 additions & 8 deletions config/schema/apigee_edge.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@ apigee_edge.common_app_settings:
type: boolean
multiple_products:
type: boolean
required_base_fields:
type: sequence
sequence:
type: string
locked_base_fields:
type: sequence
sequence:
type: string
default_products:
type: sequence
sequence:
Expand All @@ -65,8 +57,27 @@ apigee_edge.developer_app_settings:
label: 'How to refer to a Developer App on the UI (plural)'
cache_expiration:
type: integer
required_base_fields:
type: sequence
sequence:
type: string
locked_base_fields:
type: sequence
sequence:
type: string
credential_lifetime:
type: integer
callback_url_pattern:
type: string
callback_url_pattern_error_message:
type: label
label: 'Human readable description of the validation criteria that a Callback URL should match.'
callback_url_description:
type: label
label: 'Description of the Callback URL field.'
callback_url_placeholder:
type: label
label: 'Placeholder for a Callback URL'

apigee_edge.developer_settings:
type: config_object
Expand Down
9 changes: 6 additions & 3 deletions src/Entity/DeveloperApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use Apigee\Edge\Api\Management\Entity\DeveloperApp as EdgeDeveloperApp;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;

Expand Down Expand Up @@ -157,14 +158,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
])
->setLabel(t('@developer_app name', ['@developer_app' => $developer_app_singular_label]));

$definitions['callbackUrl']
$definitions['callbackUrl'] = BaseFieldDefinition::create('app_callback_url')
->setDisplayOptions('form', [
'weight' => 1,
])
->setDisplayOptions('view', [
'label' => 'inline',
'weight' => 2,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE)
->setLabel(t('Callback URL'));

$definitions['description']
Expand Down Expand Up @@ -200,8 +203,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
])
->setLabel(t('Last updated'));

$common_app_settings = \Drupal::config('apigee_edge.common_app_settings');
foreach ((array) $common_app_settings->get('required_base_fields') as $required) {
$developer_app_settings = \Drupal::config('apigee_edge.developer_app_settings');
foreach ((array) $developer_app_settings->get('required_base_fields') as $required) {
$definitions[$required]->setRequired(TRUE);
}

Expand Down
49 changes: 46 additions & 3 deletions src/Form/DeveloperAppBaseFieldConfigForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,46 @@ public function buildForm(array $form, FormStateInterface $form_state) {
}
}

foreach ($this->config('apigee_edge.common_app_settings')->get('locked_base_fields') as $locked) {
foreach ($this->config('apigee_edge.developer_app_settings')->get('locked_base_fields') as $locked) {
$form['table'][$locked]['required']['#disabled'] = TRUE;
}

$developer_app_settings = $this->config('apigee_edge.developer_app_settings');

$form['callback_url'] = [
'#type' => 'details',
'#title' => t('Callback URL settings'),
'#open' => TRUE,
'#tree' => TRUE,
];

$form['callback_url']['pattern'] = [
'#type' => 'textfield',
'#title' => $this->t('Pattern'),
'#default_value' => $developer_app_settings->get('callback_url_pattern'),
'#description' => $this->t('Regular expression that a Callback URL should match. Default is "^https?:\/\/.*$" that ensures callback url starts with either <em>http://</em> or <em>https://</em>.'),
'#required' => TRUE,
];
$form['callback_url']['pattern_error_message'] = [
'#type' => 'textfield',
'#title' => $this->t('Validation error message'),
'#default_value' => $developer_app_settings->get('callback_url_pattern_error_message'),
'#description' => $this->t('Client-side validation error message if a callback URL does not match.'),
'#required' => TRUE,
];
$form['callback_url']['description'] = [
'#type' => 'textfield',
'#title' => $this->t('Description'),
'#default_value' => $developer_app_settings->get('callback_url_description'),
'#description' => $this->t('Description of a Callback URL field.'),
];
$form['callback_url']['placeholder'] = [
'#type' => 'textfield',
'#title' => $this->t('Placeholder'),
'#default_value' => $developer_app_settings->get('callback_url_placeholder'),
'#description' => $this->t('Placeholder for a Callback URL field.'),
];

$form['save'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
Expand All @@ -81,23 +117,30 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
}
}
}

if (strpos($form_state->getValue(['callback_url', 'pattern'], ''), '^http') === FALSE) {
$form_state->setError($form['callback_url']['pattern'], $this->t('The pattern should start with <em>^http</em> to limit the acceptable protocols.'));
}
}

/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$required = [];

foreach ($form_state->getValue('table') as $name => $data) {
if ($data['required']) {
$required[] = $name;
}
}

$this->configFactory()
->getEditable('apigee_edge.common_app_settings')
->getEditable('apigee_edge.developer_app_settings')
->set('required_base_fields', $required)
->set('callback_url_pattern', $form_state->getValue(['callback_url', 'pattern']))
->set('callback_url_pattern_error_message', $form_state->getValue(['callback_url', 'pattern_error_message']))
->set('callback_url_description', $form_state->getValue(['callback_url', 'description']))
->set('callback_url_placeholder', $form_state->getValue(['callback_url', 'placeholder']))
->save();

drupal_flush_all_caches();
Expand Down
42 changes: 42 additions & 0 deletions src/Plugin/Field/FieldType/AppCallbackUrlItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* Copyright 2018 Google Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/

namespace Drupal\apigee_edge\Plugin\Field\FieldType;

use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;

/**
* App callback url specific plugin implementation of a URI item.
*
* This field is hidden on the Field UI, because it should be used as a base
* field only on company- and developer app entities.
*
* @FieldType(
* id = "app_callback_url",
* label = @Translation("App Callback URL"),
* description = @Translation("An entity field containing a callback url for an app."),
* no_ui = TRUE,
* default_formatter = "uri_link",
* default_widget = "app_callback_url",
* )
*/
class AppCallbackUrlItem extends UriItem {

}
94 changes: 94 additions & 0 deletions src/Plugin/Field/FieldWidget/AppCallbackUrlWidget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

/**
* Copyright 2018 Google Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/

namespace Drupal\apigee_edge\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\UriWidget;
use Drupal\Core\Form\FormStateInterface;

/**
* Plugin implementation of the 'app_callback_url' widget.
*
* The field supposed to be used as a base field on company- and
* developer app entities only.
* Because it should be used as a base field we can not store human readable
* strings in the widget's configuration otherwise they would not be
* translatable. (Base field definitions and configurations gets cached.)
*
* @see https://www.drupal.org/node/2546212
*
* @FieldWidget(
* id = "app_callback_url",
* label = @Translation("App Callback URL"),
* field_types = {
* "app_callback_url",
* }
* )
*/
class AppCallbackUrlWidget extends UriWidget {

/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
// Allows these to be overridden per entity programmatically
// if it is necessary.
$settings['placeholder'] = NULL;
$settings['callback_url_pattern'] = NULL;
// If you override these and the field is used as a base field then
// you can not translatable them on the UI because these values are
// being cached to the base field definition.
// @see https://www.drupal.org/node/2546212
$settings['callback_url_description'] = NULL;
$settings['callback_url_pattern_error_message'] = NULL;
return $settings;
}

/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
return [];
}

/**
* {@inheritdoc}
*/
public function settingsSummary() {
return [];
}

/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
// Try to load the (developer/company) app entity specific settings.
$app_settings = \Drupal::config("apigee_edge.{$form_state->getBuildInfo()['callback_object']->getEntity()->getEntityTypeId()}_settings");
$element['value']['#pattern'] = $this->getSetting('callback_url_pattern') ?? $app_settings->get('callback_url_pattern');
$element['value']['#attributes']['title'] = $this->getSetting('callback_url_pattern_error_message') ?? $app_settings->get('callback_url_pattern_error_message');
$element['value']['#placeholder'] = $this->getSetting('placeholder') ?? $app_settings->get('callback_url_placeholder');
$element['value']['#description'] = $this->getSetting('callback_url_description') ?? $app_settings->get('callback_url_description');
return $element;
}

}
16 changes: 16 additions & 0 deletions tests/src/Functional/ApigeeEdgeTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,20 @@ public static function getInvisibleProperty($object, $property_name) {
return $property;
}

/**
* Installs a given list of modules and rebuilds the cache.
*
* @param string[] $module_list
* An array of module names.
*
* @see \Drupal\Tests\toolbar\Functional\ToolbarCacheContextsTest::installExtraModules()
*/
protected function installExtraModules(array $module_list) {
\Drupal::service('module_installer')->install($module_list);

// Installing modules updates the container and needs a router rebuild.
$this->container = \Drupal::getContainer();
$this->container->get('router.builder')->rebuildIfNeeded();
}

}
2 changes: 1 addition & 1 deletion tests/src/Functional/ConfigurationPermissionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ConfigurationPermissionTest extends ApigeeEdgeFunctionalTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
protected static $modules = [
'block',
];

Expand Down
2 changes: 1 addition & 1 deletion tests/src/Functional/DeveloperAppFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class DeveloperAppFieldTest extends ApigeeEdgeFunctionalTestBase {

use FieldUiTestTrait;

public static $modules = [
protected static $modules = [
'link',
'block',
];
Expand Down
Loading

0 comments on commit 2263719

Please sign in to comment.