Permalink
Browse files

MINOR Initial commit

  • Loading branch information...
0 parents commit 929f8889c00c54fcce7bef4ba6edbe3f97576a44 @chillu chillu committed Jun 4, 2012
24 LICENSE
@@ -0,0 +1,24 @@
+* Copyright (c) 2012, Silverstripe Ltd.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of the <organization> nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 README.md
@@ -0,0 +1,48 @@
+# SilverStripe RestfulServer Module
+
+## Overview
+
+SOAP server class which auto-generates a WSDL file to initialize PHPs integrated `SoapServer` functionality.
+Extended by `SOAPModelAccess` to scaffold WSDL for a specific class.
+
+**This module is just a wrapper for the "[restfulserver](https://github.com/silverstripe/silverstripe-restfulserver)" module,
+internally all SOAP calls are rewritten as RESTful calls**
+
+## Requirements
+
+ * SilverStripe 3.0 or newer
+ * "[restfulserver](https://github.com/silverstripe/silverstripe-restfulserver)" module
+
+## Configuration
+
+Example DataObject with simple api access, giving full access to all object properties and relations,
+unless explicitly controlled through model permissions.
+
+ class Article extends DataObject {
+ static $db = array('Title'=>'Text','Published'=>'Boolean');
+ static $api_access = true;
+ }
+
+## Usage
+
+Getting a record:
+
+ $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
+ echo $c->getXML("MyClassName", 99); // gets record #99 as xml
+
+Updating a record:
+
+ $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
+ $data = array('MyProperty' => 'MyUpdatedValue');
+ echo $c->putXML("MyClassName", 99, null, $data);
+
+Creating a record:
+
+ $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
+ $data = array('MyProperty' => 'MyValue');
+ echo $c->putXML("MyClassName", null, null, $data);
+
+Creating a record:
+
+ $c = new SoapClient('http://mysite.com/soap/v1/wsdl');
+ echo $c->deleteXML("MyClassName");
0 _config.php
No changes.
223 code/SOAPModelAccess.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * Basic SOAP Server to access and modify DataObject instances.
+ * You can enable SOAP access on a DataObject by setting {@link DataObject::$api_access} to true.
+ * This means that you'll also enable a RESTful API through {@link RestfulServer}.
+ *
+ * @todo Test relation methods
+ *
+ * @package framework
+ * @subpackage api
+ */
+class SOAPModelAccess extends SapphireSoapServer {
+
+ public static $methods = array(
+ 'getXML' => array(
+ 'class' => 'string',
+ 'id' => 'int',
+ 'relation' => 'string',
+ '_returns' => 'string',
+ ),
+ 'getJSON' => array(
+ 'class' => 'string',
+ 'id' => 'int',
+ 'relation' => 'string',
+ '_returns' => 'string',
+ ),
+ 'putXML' => array(
+ 'class' => 'string',
+ 'id' => 'int',
+ 'relation' => 'string',
+ 'data' => 'string',
+ 'username' => 'string',
+ 'password' => 'string',
+ '_returns' => 'boolean',
+ ),
+ 'putJSON' => array(
+ 'class' => 'string',
+ 'id' => 'int',
+ 'relation' => 'string',
+ '_returns' => 'boolean',
+ ),
+ );
+
+ function Link($action = null) {
+ return Controller::join_links("soap/v1/", $action);
+ }
+
+ /**
+ * Used to emulate RESTful GET requests with XML data.
+ *
+ * @param string $class
+ * @param Number $id
+ * @param string $relation Relation name
+ * @return string
+ */
+ function getXML($class, $id, $relation = false, $username = null, $password = null) {
+ $this->authenticate($username, $password);
+
+ $response = Director::test(
+ $this->buildRestfulURL($class, $id, $relation, 'xml'),
+ null,
+ null,
+ 'GET'
+ );
+
+ return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
+ }
+
+ /**
+ * Used to emulate RESTful GET requests with JSON data.
+ *
+ * @param string $class
+ * @param Number $id
+ * @param string $relation Relation name
+ * @param string $username
+ * @param string $password
+ * @return string
+ */
+ function getJSON($class, $id, $relation = false, $username = null, $password = null) {
+ $this->authenticate($username, $password);
+
+ $response = Director::test(
+ $this->buildRestfulURL($class, $id, $relation, 'json'),
+ null,
+ null,
+ 'GET'
+ );
+
+ return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
+ }
+
+ /**
+ * Used to emulate RESTful POST and PUT requests with XML data.
+ *
+ * @param string $class
+ * @param Number $id
+ * @param string $relation Relation name
+ * @param array $data
+ * @param string $username
+ * @param string $password
+ * @return string
+ */
+ function putXML($class, $id = false, $relation = false, $data, $username = null, $password = null) {
+ $this->authenticate($username, $password);
+
+ $response = Director::test(
+ $this->buildRestfulURL($class, $id, $relation, 'xml'),
+ array(),
+ null,
+ ($id) ? 'PUT' : 'POST',
+ $data
+ );
+
+ return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
+ }
+
+ /**
+ * Used to emulate RESTful POST and PUT requests with JSON data.
+ *
+ * @param string $class
+ * @param Number $id
+ * @param string $relation Relation name
+ * @param array $data
+ * @param string $username
+ * @param string $password
+ * @return string
+ */
+ function putJSON($class = false, $id = false, $relation = false, $data, $username = null, $password = null) {
+ $this->authenticate($username, $password);
+
+ $response = Director::test(
+ $this->buildRestfulURL($class, $id, $relation, 'json'),
+ array(),
+ null,
+ ($id) ? 'PUT' : 'POST',
+ $data
+ );
+
+ return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
+ }
+
+ /**
+ * Used to emulate RESTful DELETE requests.
+ *
+ * @param string $class
+ * @param Number $id
+ * @param string $relation Relation name
+ * @param string $username
+ * @param string $password
+ * @return string
+ */
+ function deleteXML($class, $id, $relation = false, $username = null, $password = null) {
+ $this->authenticate($username, $password);
+
+ $response = Director::test(
+ $this->buildRestfulURL($class, $id, $relation, 'xml'),
+ null,
+ null,
+ 'DELETE'
+ );
+
+ return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
+ }
+
+ /**
+ * Used to emulate RESTful DELETE requests.
+ *
+ * @param string $class
+ * @param Number $id
+ * @param string $relation Relation name
+ * @param string $username
+ * @param string $password
+ * @return string
+ */
+ function deleteJSON($class, $id, $relation = false, $username = null, $password = null) {
+ $this->authenticate($username, $password);
+
+ $response = Director::test(
+ $this->buildRestfulURL($class, $id, $relation, 'json'),
+ null,
+ null,
+ 'DELETE'
+ );
+
+ return ($response->isError()) ? $this->getErrorMessage($response) : $response->getBody();
+ }
+
+ /**
+ * Faking an HTTP Basicauth login in the PHP environment
+ * that RestfulServer can pick up.
+ *
+ * @param string $username Username
+ * @param string $password Plaintext password
+ */
+ protected function authenticate($username, $password) {
+ if(is_string($username)) $_SERVER['PHP_AUTH_USER'] = $username;
+ if(is_string($password)) $_SERVER['PHP_AUTH_PW'] = $password;
+ }
+
+ /**
+ * @param string $class
+ * @param Number $id
+ * @param string $relation
+ * @param string $extension
+ * @return string
+ */
+ protected function buildRestfulURL($class, $id, $relation, $extension) {
+ $url = "api/v1/{$class}";
+ if($id) $url .= "/{$id}";
+ if($relation) $url .= "/{$relation}";
+ if($extension) $url .= "/.{$extension}";
+ return $url;
+ }
+
+ /**
+ * @param SS_HTTPResponse $response
+ * @return string XML string containing the HTTP error message
+ */
+ protected function getErrorMessage($response) {
+ return "<error type=\"authentication\" code=\"" . $response->getStatusCode() . "\">" . $response->getStatusDescription() . "</error>";
+ }
+}
+
103 code/SapphireSoapServer.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Soap server class which auto-generates a WSDL
+ * file to initialize PHPs integrated {@link SoapServer} class.
+ *
+ * See {@link SOAPModelAccess} for an auto-generated SOAP API for your models.
+ *
+ * @todo Improve documentation
+ * @package framework
+ * @subpackage integration
+ */
+class SapphireSoapServer extends Controller {
+
+ /**
+ * @var array Map of method name to arguments.
+ */
+ static $methods = array();
+
+ /**
+ * @var array
+ */
+ static $xsd_types = array(
+ 'int' => 'xsd:int',
+ 'boolean' => 'xsd:boolean',
+ 'string' => 'xsd:string',
+ 'binary' => 'xsd:base64Binary',
+ );
+
+ static $allowed_actions = array(
+ 'index',
+ 'wsdl'
+ );
+
+ function wsdl() {
+ $this->getResponse()->addHeader("Content-Type", "text/xml");
+
+ return array();
+ }
+
+ /**
+ * @return string
+ */
+ function getWSDLURL() {
+ return Director::absoluteBaseURLWithAuth() . $this->Link() . "wsdl";
+ }
+
+ /**
+ * @return SS_List Collection of ArrayData elements describing
+ * the method (keys: 'Name', 'Arguments', 'ReturnType')
+ */
+ function Methods() {
+ $methods = array();
+
+ foreach($this->stat('methods') as $methodName => $arguments) {
+ $returnType = $arguments['_returns'];
+ unset($arguments['_returns']);
+
+ $processedArguments = array();
+ foreach($arguments as $argument => $type) {
+ $processedArguments[] = new ArrayData(array(
+ "Name" => $argument,
+ "Type" => self::$xsd_types[$type],
+ ));
+
+ }
+ $methods[] = new ArrayData(array(
+ "Name" => $methodName,
+ "Arguments" => new ArrayList($processedArguments),
+ "ReturnType" => self::$xsd_types[$returnType],
+ ));
+ }
+
+ return new ArrayList($methods);
+ }
+
+ /**
+ * @return string
+ */
+ function TargetNamespace() {
+ return Director::absoluteBaseURL();
+ }
+
+ /**
+ * @return string
+ */
+ function ServiceURL() {
+ return Director::absoluteBaseURLWithAuth() . $this->class . '/';
+ }
+
+ function index() {
+ $wsdl = $this->getViewer('wsdl')->process($this);
+ $wsdlFile = TEMP_FOLDER . '/sapphire-wsdl-' . $this->class;
+ $fh = fopen($wsdlFile, 'w');
+ fwrite($fh, $wsdl);
+ fclose($fh);
+
+ $s = new SoapServer($wsdlFile, array('cache_wsdl' => WSDL_CACHE_NONE));
+ $s->setClass($this->class);
+ $s->handle();
+ }
+}
+
+
46 code/SapphireSoapServer_wsdl.ss
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
+xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+xmlns:tns="{$ServiceURL}wsdl"
+targetNamespace="{$ServiceURL}wsdl">
+ <% loop Methods %>
+ <message name="{$Name}Request" targetNamespace="$CurrentPage.TargetNamespace">
+ <% loop Arguments %>
+ <part name="$Name" type="$Type"/>
+ <% end_loop %>
+ </message>
+ <message name="{$Name}Response" targetNamespace="$CurrentPage.TargetNamespace">
+ <part name="{$Name}Return" type="$ReturnType" />
+ </message>
+ <% end_loop %>
+
+ <portType name="SapphireSOAP_methodsPortType">
+ <% loop Methods %>
+ <operation name="$Name">
+ <input message="tns:{$Name}Request"/>
+ <output message="tns:{$Name}Response"/>
+ </operation>
+ <% end_loop %>
+ </portType>
+ <binding name="SapphireSOAP_methodsBinding" type="tns:SapphireSOAP_methodsPortType">
+ <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+ <% loop Methods %>
+ <operation name="$Name">
+ <soap:operation soapAction="$CurrentPage.ServiceURL?method=$Name" style="rpc"/>
+ <input>
+ <soap:body use="encoded" namespace="$CurrentPage.TargetNamespace" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
+ </input>
+ <output>
+ <soap:body use="encoded" namespace="$CurrentPage.TargetNamespace" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
+ </output>
+ </operation>
+ <% end_loop %>
+ </binding>
+ <service name="SapphireSOAP_methods">
+ <port name="SapphireSOAP_methodsPort" binding="tns:SapphireSOAP_methodsBinding">
+ <soap:address location="$CurrentPage.ServiceURL" />
+ </port>
+ </service>
+</definitions>
+
31 tests/SapphireSoapServerTest.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @package framework
+ * @subpackage tests
+ */
+class SapphireSoapServerTest extends FunctionalTest {
+
+ /**
+ * @see http://open.silverstripe.com/ticket/4570
+ */
+ function testWsdl() {
+ $response = $this->get('SapphireSoapServerTest_MyServer/wsdl');
+
+ $this->assertEquals(
+ $response->getHeader('Content-Type'),
+ 'text/xml',
+ 'wsdl request returns with correct XML content type'
+ );
+ }
+}
+
+/**
+ * @package framework
+ * @subpackage tests
+ */
+class SapphireSoapServerTest_MyServer extends SapphireSoapServer {
+
+ function Link($action = null) {
+ return Controller::join_links('SapphireSoapServerTest_MyServer', $action);
+ }
+}
162 tests/SoapModelAccessTest.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ *
+ * @todo Test Relation getters
+ * @todo Test filter and limit through GET params
+ * @todo Test DELETE verb
+ *
+ * @package framework
+ * @subpackage testing
+ */
+class SoapModelAccessTest extends SapphireTest {
+
+ static $fixture_file = 'SoapModelAccessTest.yml';
+
+ protected $extraDataObjects = array(
+ 'SoapModelAccessTest_Comment',
+ 'SoapModelAccessTest_Page',
+ );
+
+ public function getTestSoapConnection() {
+ // We can't actually test the SOAP server itself because there's not currently a way of putting it into "test mode"
+ return new SOAPModelAccess();
+
+ // One day, we should build this facility and then return something more like the item below:
+ // return new SoapClient(Director::absoluteBaseURL() . 'soap/v1/wsdl');
+ }
+
+ public function testApiAccess() {
+ $c = $this->getTestSoapConnection();
+ $soapResponse = $c->getXML(
+ "SoapModelAccessTest_Comment",
+ 1,
+ null,
+ null,
+ 'editor@test.com',
+ 'editor'
+ );
+
+ $responseArr = Convert::xml2array($soapResponse);
+ $this->assertEquals($responseArr['ID'], 1);
+ $this->assertEquals($responseArr['Name'], 'Joe');
+ }
+
+ public function testAuthenticatedPUT() {
+ $comment1 = $this->objFromFixture('SoapModelAccessTest_Comment', 'comment1');
+ $comment1ID = $comment1->ID;
+
+ // test wrong details
+ $c = $this->getTestSoapConnection();
+
+ $updateXML = <<<XML
+<?xml version="1.0" encoding="UTF-8"?>
+ <SoapModelAccessTest_Comment>
+ <ID>$comment1ID</ID>
+ <Name>Jimmy</Name>
+ </SoapModelAccessTest_Comment>
+XML;
+
+ $soapResponse = $c->putXML(
+ "SoapModelAccessTest_Comment",
+ $comment1->ID,
+ null,
+ $updateXML,
+ 'editor@test.com',
+ 'wrongpassword'
+ );
+ $this->assertEquals('<error type="authentication" code="401">Unauthorized</error>', $soapResponse);
+
+ // Check that the details weren't saved
+ $c = $this->getTestSoapConnection();
+ $soapResponse = $c->getXML("SoapModelAccessTest_Comment", $comment1->ID, null, 'editor@test.com', 'editor');
+ $responseArr = Convert::xml2array($soapResponse);
+ $this->assertEquals($comment1->ID, $responseArr['ID']);
+ $this->assertEquals('Joe', $responseArr['Name']);
+
+ // Now do an update with the right password
+ $soapResponse = $c->putXML(
+ "SoapModelAccessTest_Comment",
+ $comment1->ID,
+ null,
+ $updateXML,
+ 'editor@test.com',
+ 'editor'
+ );
+
+ // Check that the details were saved
+ $c = $this->getTestSoapConnection();
+ $soapResponse = $c->getXML("SoapModelAccessTest_Comment", $comment1->ID, null, 'editor@test.com', 'editor');
+ $responseArr = Convert::xml2array($soapResponse);
+ $this->assertEquals($comment1->ID, $responseArr['ID']);
+ $this->assertEquals('Jimmy', $responseArr['Name']);
+ }
+
+ public function testAuthenticatedPOST() {
+ /*
+ $c = $this->getTestSoapConnection();
+ $soapResponse = $c->getXML(
+ "SoapModelAccessTest_Comment",
+ null,
+ null,
+ 'editor@test.com',
+ 'editor'
+ );
+ Debug::message($soapResponse);
+ $responseArr = Convert::xml2array($soapResponse);
+ Debug::show($responseArr);
+ $this->assertEquals($responseArr['Name'], 'Created Name');
+ */
+ }
+}
+
+/**
+ * Everybody can view comments, logged in members in the "users" group can create comments,
+ * but only "editors" can edit or delete them.
+ *
+ */
+class SoapModelAccessTest_Comment extends DataObject implements PermissionProvider,TestOnly {
+
+ static $api_access = true;
+
+ static $db = array(
+ "Name" => "Varchar(255)",
+ "Comment" => "Text"
+ );
+
+ static $has_many = array();
+
+ public function providePermissions(){
+ return array(
+ 'EDIT_Comment' => 'Edit Comment Objects',
+ 'CREATE_Comment' => 'Create Comment Objects',
+ 'DELETE_Comment' => 'Delete Comment Objects',
+ );
+ }
+
+ public function canView($member = null) {
+ return true;
+ }
+
+ public function canEdit($member = null) {
+ return Permission::checkMember($member, 'EDIT_Comment');
+ }
+
+ public function canDelete($member = null) {
+ return Permission::checkMember($member, 'DELETE_Comment');
+ }
+
+ public function canCreate($member = null) {
+ return Permission::checkMember($member, 'CREATE_Comment');
+ }
+
+}
+
+class SoapModelAccessTest_Page extends DataObject implements TestOnly {
+
+ static $api_access = false;
+
+ static $db = array(
+ 'Title' => 'Text',
+ 'Content' => 'HTMLText',
+ );
+}
38 tests/SoapModelAccessTest.yml
@@ -0,0 +1,38 @@
+SoapModelAccessTest_Comment:
+ comment1:
+ Name: Joe
+ Comment: This is a test comment
+Member:
+ editor:
+ FirstName: Editor
+ Email: editor@test.com
+ Password: editor
+ user:
+ FirstName: User
+ Email: user@test.com
+ Password: user
+Group:
+ editorgroup:
+ Title: Editors
+ Code: editors
+ Members: =>Member.editor
+ usergroup:
+ Title: Users
+ Code: users
+ Members: =>Member.user
+Permission:
+ perm1:
+ Code: CREATE_Comment
+ Group: =>Group.usergroup
+ perm3:
+ Code: EDIT_Comment
+ Group: =>Group.editorgroup
+ perm4:
+ Code: DELETE_Comment
+ Group: =>Group.editorgroup
+ perm5:
+ Code: CREATE_Comment
+ Group: =>Group.editorgroup
+SoapModelAccessTest_Page:
+ page1:
+ Title: Testpage without API Access

0 comments on commit 929f888

Please sign in to comment.