Git Product home page Git Product logo

Comments (12)

jfritschi avatar jfritschi commented on May 28, 2024

Maybe i'm missing something but isn't phpCAS working exacly how one would expect?

You proxy another url that tries to redirect phpCAS to another url. Since you are asking phpCAS to POST stuff to your url (sent in the HTTP header of the request) there is no way to know in advance if the url is a redirect. So the POST data is sent every time. Since phpCAS recieves a HTTP header redirect it has to assume that the POST data is lost and was not processed properly.
As a safety measure Adam correctly added safety limits for redirects into that process to prevent loops. One might argue about lowering it more but this might not work in all setups and a higher value simply causes slighly more calls in loop setups.

I don't see any way to improve this very generic and safe setup. Maybe there might be more optimized setups possible if you know the target architecture but this won't work in general.

Happy to hear more arguments but currently a fail to see your point. Your example if crafted explicitly to loop if POST data is submitted and will therefore loop. If we would not send the POST data on the first call to check for redirects it might not work properly: The redirect might depend on the POST data and this would introduce extra requests in simple setups.

from phpcas.

antoyo avatar antoyo commented on May 28, 2024

I actually have this problem because the service is a web form.
If we forget that this is a proxy service, when the form is submitted, there is a redirection to the same page (the web form) to avoid submitting data when refreshing the page.
This redirection does not send the data again, so I expect phpCAS to do the same.

from phpcas.

jfritschi avatar jfritschi commented on May 28, 2024

Well i don't see how we can fix that with anything but a "switch" within our ProxiedServices to stop following redirects. Don't know if that is a good idea. Your setup somehow sounds strange but maybe i'm not really understanding your setup.

Maybe @adamfranco has another idea since he implemented the ProxiedServices.

But you can always just override the phpCAS classes with your own implementation of the Post implementation. Don't think there is much documentation on how to do it but adam made everything neat and object oriented so that you can override and exchange all classes. Just implement your own class implementing the CAS_ProxiedService_Http_Abstract interface or extend and override parts of our CAS_ProxiedService_Http_Post implementation and you can use the class instead of the phpCAS::getProxiedService() call.

Probably the cleanest solution until we figure out how to deal with this issue.

from phpcas.

adamfranco avatar adamfranco commented on May 28, 2024

@antoyo, If I understand correctly, you have a resource that lives at a URL x that responds differently to GET and POST requests and redirects back to itself with an HTTP 302 after handling the POST. This would make the intended flow something like the following:

GET x (returns the form) --> POST x (submits the form) --> 302 or 303, Location: x --> GET x (returns the form/status message).

Since you probably know the form contents, you would likely skip the first GET and just want to do:

POST x (submits the form) --> 302 or 303, Location: x --> GET x (returns the form/status message).

The behavior you are seeing though is:

POST x (submits the form) --> 302 or 303, Location: x --> POST x (submits the form) --> 302 or 303, Location: x --> .....

Is that correct?

In reading up on the HTTP 300 codes it seems that conversion from POST to GET on redirect depends on usage of 303/307 versus 302. A 302 response to a POST could result in another POST request to the Location, while a 303 response must be converted to a GET.

If I am reading the HTTP/1.1 spec correctly, what we should do is make additional POST requests to the new location when receiving a 302, but convert to a GET request when receiving a 303. That said, the spec notes that many user-agents treat 302s like 303s and hence the behavior is ambiguous. If your application is returning a 302, then the current behavior is likely to spec, but you can implement your own CAS_ProxiedService_Http class (which could extend CAS_ProxiedService_Http_Abstract) to get the behavior you want. Let me know if you have trouble figuring out how to do this and I will write up some documentation on the wiki.

from phpcas.

antoyo avatar antoyo commented on May 28, 2024

@adamfranco, you are right.

I came up to this code:

<?php

require_once  'path/to/phpCAS/CAS/ProxiedService/Http/Post.php';

class CasProxiedServiceHttpPost extends CAS_ProxiedService_Http_Post {
  /**
   * Indicator of the number of requests (including redirects performed.
   *
    * @var int $_numRequests;  
   */
  protected $_numRequests = 0;

  /**
   * The response headers.
   *
   * @var array $_responseHeaders;  
   */
  protected $_responseHeaders = array();

  /**
   * The response status code.
   *
   * @var string $_responseStatusCode;  
   */
  protected $_responseStatusCode = '';

  /**
   * The response headers.
   *
   * @var string $_responseBody;  
   */
  protected $_responseBody = '';

  public static function getCookieJar() {
    if (!isset($_SESSION['phpCAS'])) {
      $_SESSION['phpCAS'] = array();
    }
    if (!isset($_SESSION['phpCAS']['service_cookies'])) {
      $_SESSION['phpCAS']['service_cookies'] = array();
    }
    return new CAS_CookieJar($_SESSION['phpCAS']['service_cookies']);
  }

  /**
   * Answer a proxy-authenticated service handler.
   *      
   * @return CasProxiedServiceHttpPost
   */
  public static function getProxiedService () {
    global $PHPCAS_CLIENT;

    $request = new CAS_CurlRequest();
    $proxiedService = new CasProxiedServiceHttpPost($request,
        CasProxiedServiceHttpPost::getCookieJar());
    if ($proxiedService instanceof CAS_ProxiedService_Testable) {
      $proxiedService->setCasClient($PHPCAS_CLIENT);
    }
    return $proxiedService;
  }

  /**
   * Answer true if our request has been sent yet.
   * 
   * @return boolean
   */
  protected function hasBeenSent () {
    return ($this->_numRequests > 0);
  }

  /**
   * Build and perform a request, do not follow redirects
   * 
   * @param string $url
   * @return void
   * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
   *      The code of the Exception will be one of: 
   *          PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE 
   *          PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
   *          PHPCAS_SERVICE_PT_FAILURE
   * @throws CAS_ProxiedService_Exception If there is a failure sending the request to the target service.
   */
  protected function makeRequest ($url) {
    $this->_numRequests++;

    // Create a new request.
    $request = clone $this->_requestHandler;
    $request->setUrl($url);

    // Add any cookies to the request.
    $cookieJar = CasProxiedServiceHttpPost::getCookieJar();
    $request->addCookies($cookieJar->getCookies($url));

    // Add any other parts of the request needed by concrete classes
    $this->populateRequest($request);

    // Perform the request.
    phpCAS::trace('Performing proxied service request to \''.$url.'\'');
    if (!$request->send()) {
      $message = 'Could not perform proxied service request to URL`'.$url.'\'. '.$request->getErrorMessage();
      phpCAS::trace($message);
      throw new CAS_ProxiedService_Exception($message);
    }

    // Store any cookies from the response;
    $cookieJar->storeCookies($url, $request->getResponseHeaders());

    $this->_responseHeaders = $request->getResponseHeaders();
    $this->_responseBody = $request->getResponseBody();
    $this->_responseStatusCode = $request->getResponseStatusCode();
  }

  /**
   * Answer the headers of the response.
   *
   * @return array An array of header strings.
   * @throws CAS_OutOfSequenceException If called before the Request has been sent.
   */
  public function getResponseHeaders () {
    if (!$this->hasBeenSent())
      throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');

    return $this->_responseHeaders;
  }

  /**
   * Answer HTTP status code of the response
   *
   * @return integer
   * @throws CAS_OutOfSequenceException If called before the Request has been sent.
   */
  public function getResponseStatusCode () {
    if (!$this->hasBeenSent())
      throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');

      return $this->_responseStatusCode;
  }

  /**
   * Answer the body of response.
   *
   * @return string
   * @throws CAS_OutOfSequenceException If called before the Request has been sent.
   */
  public function getResponseBody () {
    if (!$this->hasBeenSent())
      throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');

    return $this->_responseBody;
  }
}

It is quite painful because some attributes are private instead of protected:
I need to redefine functions that already exist.

I don't know if it is a common idiom, but it would be nice if it could be less code to achive that small modification.

Moreover, because http codes seem to be misused, it would be nice if I could tell phpCAS to do not post on redirect.

from phpcas.

adamfranco avatar adamfranco commented on May 28, 2024

It might make more sense (for this particular case) to just change the visibility of CAS_ProxiedService_Http_Abstract::getRedirectUrl (array $responseHeaders) from private to protected. This would allow you to always return null for that one function to prevent redirecting. If this looks like it would be sufficient for you I can push this update. There isn't really any need for getRedirectUrl() to be private since it just looks at the input and returns a scalar, I just did so because I didn't see any utility in making it visible to child classes at the time.

Taking this a step further toward alternative handling of 303/307 responses: if we wanted to change subsequent requests to 303 responses from POST to GET we should be able to do that in CAS_ProxiedService_Http_Post::populateRequest (CAS_RequestInterface $request) by inspecting the value of $this->hasBeenSent() and $this->getResponseStatusCode() before we do:

    $request->makePost();
    if (!empty($this->_body)) {
        $request->addHeader('Content-Type: '.$this->_contentType);
        $request->addHeader('Content-Length: '.strlen($this->_body));
        $request->setPostBody($this->_body);
    }

from phpcas.

antoyo avatar antoyo commented on May 28, 2024

Changing the visibility of getRedirectUrl() to protected would help a lot.

from phpcas.

antoyo avatar antoyo commented on May 28, 2024

I now use the latest version of phpCAS (1.3.0) and my code does not work anymore.

I am unable to get my custom proxied service using this static method:

<?php

  public static function getProxiedService () {
    global $PHPCAS_CLIENT;

    $request = new CAS_CurlRequest();
    $proxiedService = new CasProxiedServiceHttpPost($request,
        CasProxiedServiceHttpPost::getCookieJar());
    if ($proxiedService instanceof CAS_ProxiedService_Testable) {
      $proxiedService->setCasClient($PHPCAS_CLIENT);
    }
    return $proxiedService;
  }

This is because $PHPCAS_CLIENT is not a global variable anymore, it is a private static attribute so I can't access to it.
Is there a way to access to the current phpCAS client?
If not, do you know how I can create my custom proxied service?
Thanks for your help.

from phpcas.

adamfranco avatar adamfranco commented on May 28, 2024

As of phpCAS 1.3.0 there is intentionally no way to access the $PHPCAS_CLIENT. Having the client as a publicly accessible global had two major downsides: phpCAS users would work with the client directly preventing us from being able to change the client's internal behavior without breaking things for users; as well, using globals made it hard or impossible to write solid unit tests.

You can initialize arbitrary CAS_ProxiedService implementations using phpCAS::initializeProxiedService($myProxiedService). This should be equivalent to using phpCAS::getProxiedService($type) to access one of the distributed implementations. Unfortunately, in researching what is actually going on in these functions I see that the distributed ProxiedService implementations are being given special access to the CAS_Client and aren't making use of the API themselves. I will look into refactoring the distributed CAS_ProxiedService implementations so that they do not have special access to the CAS_Client that extensions of them can't use.

from phpcas.

jfritschi avatar jfritschi commented on May 28, 2024

@adamfranco: Can we close this issues or is there still some work pending?

I would like to release a new version asap to fix the pear issue and this issue is holding me back currently.

from phpcas.

adamfranco avatar adamfranco commented on May 28, 2024

There is still work pending in that I need to refactor the distributed ProxiedService implementations to not rely on special access to the CAS_Client object. This work will take a bit of time though, so don't let this issue hold back a release.

from phpcas.

jfritschi avatar jfritschi commented on May 28, 2024

Ok, i will just wait for some feedback regarding #36 and tag a new release.

from phpcas.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.