diff --git a/.gitignore b/.gitignore index b2dc20145..ae4975b08 100755 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ coverage local/cache/* composer.lock web/assets/* +web/.htaccess diff --git a/composer.json b/composer.json index 9cc776517..81055efd8 100755 --- a/composer.json +++ b/composer.json @@ -27,8 +27,6 @@ "symfony/form": "2.2.*", "symfony/validator": "2.2.*", - "symfony/security": "2.2.*", - "symfony/templating": "2.2.*", "smarty/smarty": "v3.1.13", "kriswallsmith/assetic": "1.2.*@dev", diff --git a/core/lib/Thelia/Action/Customer.php b/core/lib/Thelia/Action/Customer.php index 48c2a2e0e..186b8e34c 100755 --- a/core/lib/Thelia/Action/Customer.php +++ b/core/lib/Thelia/Action/Customer.php @@ -25,13 +25,30 @@ namespace Thelia\Action; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Thelia\Core\Event\ActionEvent; +use Thelia\Form\BaseForm; +use Thelia\Form\CustomerCreation; class Customer implements EventSubscriberInterface { public function create(ActionEvent $event) { + $request = $event->getRequest(); + $customerForm = new CustomerCreation($request); + + $form = $customerForm->getForm(); + + + if ($request->isMethod("post")) { + $form->bind($request); + + if ($form->isValid()) { + echo "ok"; exit; + } else { + echo "ko"; exit; + } + } } public function modify(ActionEvent $event) diff --git a/core/lib/Thelia/Admin/Controller/AdminController.php b/core/lib/Thelia/Admin/Controller/AdminController.php index bb5536c7e..38084ba89 100755 --- a/core/lib/Thelia/Admin/Controller/AdminController.php +++ b/core/lib/Thelia/Admin/Controller/AdminController.php @@ -28,17 +28,23 @@ use Thelia\Form\AdminLogin; class AdminController extends BaseAdminController { - public function indexAction() + public function loginAction() { - $form = $this->getLoginForm(); $request = $this->getRequest(); if($request->isMethod("POST")) { + $form->bind($request); - if($form->isValid()) { + if ($form->isValid()) { + + $this->container->get('request')->authenticate( + $form->get('username')->getData(), + $form->get('password')->getData() + ); + echo "valid"; exit; } } @@ -48,13 +54,20 @@ class AdminController extends BaseAdminController { )); } + public function indexAction() + { + $form = $this->getLoginForm(); + + return $this->render("login.html", array( + "form" => $form->createView() + )); + } + protected function getLoginForm() { - $form = $this->getFormBuilder(); + $adminLogin = new AdminLogin($this->getRequest()); - $adminLogin = new AdminLogin(); - - return $adminLogin->buildForm($form, array())->getForm(); + return $adminLogin->getForm(); } public function lostAction() diff --git a/core/lib/Thelia/Admin/Controller/BaseAdminController.php b/core/lib/Thelia/Admin/Controller/BaseAdminController.php index 3aa0e9281..f63f87d21 100755 --- a/core/lib/Thelia/Admin/Controller/BaseAdminController.php +++ b/core/lib/Thelia/Admin/Controller/BaseAdminController.php @@ -48,7 +48,7 @@ class BaseAdminController extends ContainerAware */ public function render($templateName, $args = array()) { - $args = array_merge($args, array('lang' => 'fr')); + $args = array_merge($args, array('lang' => 'fr')); // FIXME $response = new Response(); @@ -57,7 +57,7 @@ class BaseAdminController extends ContainerAware public function renderRaw($templateName, $args = array()) { - $args = array_merge($args, array('lang' => 'fr')); + $args = array_merge($args, array('lang' => 'fr')); // FIXME return $this->getParser()->render($templateName, $args); } @@ -90,5 +90,6 @@ class BaseAdminController extends ContainerAware return $this->getFormFactory()->createBuilder("form"); } - + protected function isGranted() { + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php b/core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php new file mode 100644 index 000000000..eb60f0482 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php @@ -0,0 +1,60 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Authentication; + +use Thelia\Core\Security\UserNotFoundException; +use Thelia\Core\Security\IncorrectPasswordException; + +/** + * Aunthentication providers are in charge or retrieving users, and check their + * credentials. + * + * @author Franck + * + */ +interface AuthenticationProviderInterface { + + /** + * Set the authentication token + * + * @param TokenInterface $token the authentication token + */ + public function setToken(TokenInterface $token); + + + /** + * Set the authentication token + * + * @param unknown $key + */ + public function supportsToken(TokenInterface $token); + + /** + * Authenticate the token + * + *@throws Exception if authentication was not successful + */ + public function authenticate(); +} +?> \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php b/core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php new file mode 100644 index 000000000..f6161f038 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php @@ -0,0 +1,67 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Authentication; + +use Thelia\Core\Security\Authentication\AuthenticationProviderInterface; +use Thelia\Core\Security\Encoder\PasswordEncoderInterface; +use Thelia\Core\Security\User\UserProviderInterface; +use Thelia\Security\Token\TokenInterface; +use Thelia\Core\Security\Exception\IncorrectPasswordException; +use Thelia\Core\Security\Token\UsernamePasswordToken; + +class UsernamePasswordAuthenticator implements AuthenticationProviderInterface { + + protected $userProvider; + protected $encoder; + + private $token; + + public function __construct(UserProviderInterface $userProvider, PasswordEncoderInterface $encoder) { + $this->userProvider = $userProvider; + $this->encoder = $encoder; + } + + public function supportsToken(TokenInterface $token) { + + return $token instanceof UsernamePasswordToken; + } + + public function authenticate($token) { + + if (!$this->supports($token)) { + return null; + } + + // Retreive user + $user = $this->userProvider->getUser($this->token->getUsername()); + + // Check password + $authOk = $this->encoder->isEqual($password, $user->getPassword(), $user->getAlgo(), $user->getSalt()) === true; + + $authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $authOk); + + return $authenticatedToken; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php new file mode 100644 index 000000000..3353969b8 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php @@ -0,0 +1,52 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Encoder; + +/** + * This interface defines a password encoder. + * + * @author Franck Allimant + * + */ +interface PasswordEncoderInterface { + + /** + * Encode a string. + * + * @param string $password the password to encode + * @param string $algorithm the hash() algorithm + * @return string $salt the salt + */ + public function encode($password, $algorithm, $salt); + + /** + * Check a string against an encoded password. + * + * @param string $string the string to compare against password + * @param string $password the encoded password + * @param string $algorithm the hash() algorithm + * @return string $salt the salt + */ + public function isEqual($string, $password, $algorithm, $salt); +} diff --git a/core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php new file mode 100644 index 000000000..7a66812f5 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Encoder; + +/** + * This interface defines a hash based password encoder. + * + * @author Franck Allimant + */ + +class PasswordHashEncoder implements PasswordEncoderInterface { + + /** + * {@inheritdoc} + */ + public function encode($password, $algorithm, $salt) + { + if (!in_array($algorithm, hash_algos(), true)) { + throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $algorithm)); + } + + // Salt the string + $salted = $password.$salt; + + // Create the hash + $digest = hash($algorithm, $salted, true); + + // "stretch" hash + for ($i = 1; $i < 5000; $i++) { + $digest = hash($algorithm, $digest.$salted, true); + } + + return base64_encode($digest); + } + + /** + * {@inheritdoc} + */ + public function isEqual($string, $password, $algorithm, $salt) + { + $encoded = $this->encode($password, $algorithm, $salt); + + return $encoded == $string; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php b/core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php new file mode 100644 index 000000000..061b43257 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php @@ -0,0 +1,28 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Exception; + +class AuthenticationTokenNotFoundException extends \Exception +{ +} diff --git a/core/lib/Thelia/Core/Security/Security/SecurityManager.php b/core/lib/Thelia/Core/Security/Security/SecurityManager.php new file mode 100644 index 000000000..82c45a304 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/SecurityManager.php @@ -0,0 +1,81 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security; + +use Thelia\Core\Security\Authentication\AuthenticationProviderInterface; +use Thelia\Core\Security\Exception\AuthenticationTokenNotFoundException; + +/** + * A simple security manager, in charge of authenticating users using various authentication systems. + * + * @author Franck Allimant + */ +class SecurityManager { + + protected $authProvider; + + public function __construct(AuthenticationProviderInterface $authProvider) { + $this->authProvider = $authProvider; + } + + /** + * Checks if the current token is authenticated + * + * @throws AuthenticationCredentialsNotFoundException when the security context has no authentication token. + * + * @return Boolean + */ + final public function isGranted() + { + if (null === $this->token) { + throw new AuthenticationTokenNotFoundException('The security context contains no authentication token.'); + } + + if (!$this->token->isAuthenticated()) { + $this->token = $this->authProvider->authenticate($this->token); + } + + return $this->token->isAuthenticated(); + } + + /** + * Gets the currently authenticated token. + * + * @return TokenInterface|null A TokenInterface instance or null if no authentication information is available + */ + public function getToken() + { + return $this->token; + } + + /** + * Sets the token. + * + * @param TokenInterface $token A TokenInterface token, or null if no further authentication information should be stored + */ + public function setToken(TokenInterface $token = null) + { + $this->token = $token; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php b/core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php new file mode 100644 index 000000000..be1f8cb8b --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php @@ -0,0 +1,148 @@ + +* @author Johannes M. Schmitt +*/ +abstract class AbstractToken implements TokenInterface +{ + private $user; + private $authenticated; + + /** + * Constructor. + * + * @param RoleInterface[] $roles An array of roles + * + * @throws \InvalidArgumentException + */ + public function __construct() + { + $this->authenticated = false; + } + + /** + * {@inheritdoc} + */ + public function getUsername() + { + if ($this->user instanceof UserInterface) { + return $this->user->getUsername(); + } + + return (string) $this->user; + } + + public function getUser() + { + return $this->user; + } + + /** + * Sets the user in the token. + * + * The user can be a UserInterface instance, or an object implementing + * a __toString method or the username as a regular string. + * + * @param mixed $user The user + * @throws \InvalidArgumentException + */ + public function setUser($user) + { + if (!($user instanceof UserInterface || is_string($user))) { + throw new \InvalidArgumentException('$user must be an instanceof UserInterface, or a primitive string.'); + } + + if (null === $this->user) { + $changed = false; + } elseif ($this->user instanceof UserInterface) { + if (!$user instanceof UserInterface) { + $changed = true; + } else { + $changed = $this->hasUserChanged($user); + } + } elseif ($user instanceof UserInterface) { + $changed = true; + } else { + $changed = (string) $this->user !== (string) $user; + } + + if ($changed) { + $this->setAuthenticated(false); + } + + $this->user = $user; + } + + /** + * {@inheritdoc} + */ + public function isAuthenticated() + { + return $this->authenticated; + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($authenticated) + { + $this->authenticated = (Boolean) $authenticated; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + if ($this->getUser() instanceof UserInterface) { + $this->getUser()->eraseCredentials(); + } + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array($this->user, $this->authenticated)); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + list($this->user, $this->authenticated) = unserialize($serialized); + } + + private function hasUserChanged(UserInterface $user) + { + if (!($this->user instanceof UserInterface)) { + throw new \BadMethodCallException('Method "hasUserChanged" should be called when current user class is instance of "UserInterface".'); + } + + if ($this->user instanceof EquatableInterface) { + return ! (Boolean) $this->user->isEqualTo($user); + } + + if ($this->user->getPassword() !== $user->getPassword()) { + return true; + } + + if ($this->user->getSalt() !== $user->getSalt()) { + return true; + } + + if ($this->user->getUsername() !== $user->getUsername()) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php b/core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php new file mode 100644 index 000000000..d70c8eeea --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php @@ -0,0 +1,81 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Token; + +/** + * TokenInterface is the interface for the user authentication information. + * + * Parts borrowed from Symfony Security Framework (Fabien Potencier / Johannes M. Schmitt ) + */ + +interface TokenInterface extends \Serializable +{ + /** + * Returns the user credentials. + * + * @return mixed The user credentials + */ + public function getCredentials(); + + /** + * Returns a user representation. + * + * @return mixed either returns an object which implements __toString(), or + * a primitive string is returned. + */ + public function getUser(); + + /** + * Sets a user instance + * + * @param mixed $user + */ + public function setUser($user); + + /** + * Returns the username. + * + * @return string + */ + public function getUsername(); + + /** + * Returns whether the user is authenticated or not. + * + * @return Boolean true if the token has been authenticated, false otherwise + */ + public function isAuthenticated(); + + /** + * Sets the authenticated flag. + * + * @param Boolean $isAuthenticated The authenticated flag + */ + public function setAuthenticated($isAuthenticated); + + /** + * Removes sensitive information from the token. + */ + public function eraseCredentials(); +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php b/core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php new file mode 100644 index 000000000..f605d1767 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php @@ -0,0 +1,75 @@ + +*/ +class UsernamePasswordToken extends AbstractToken +{ + private $credentials; + + /** + * Constructor. + * + * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. + * @param string $password The password of the user + * + * @throws \InvalidArgumentException + */ + public function __construct($username, $password, $authenticated = false) + { + $this->setUser($username); + $this->credentials = $password; + + parent::setAuthenticated($authenticated); + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($isAuthenticated) + { + if ($isAuthenticated) { + throw new \LogicException('Cannot set this token to trusted after instantiation.'); + } + + parent::setAuthenticated(false); + } + + public function getCredentials() + { + return $this->credentials; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + parent::eraseCredentials(); + + $this->credentials = null; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array($this->credentials, $this->providerKey, parent::serialize())); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized); + parent::unserialize($parentStr); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php b/core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php new file mode 100644 index 000000000..9df5ee484 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php @@ -0,0 +1,18 @@ +filterByLogin($key) + ->findOne(); + + return $admin; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php b/core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php new file mode 100644 index 000000000..824d9e774 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php @@ -0,0 +1,20 @@ +filterByEmail($key) + ->findOne(); + + return $customer; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/UserInterface.php b/core/lib/Thelia/Core/Security/Security/User/UserInterface.php new file mode 100644 index 000000000..9057504ca --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/UserInterface.php @@ -0,0 +1,42 @@ + + * + */ +interface UserInterface { + + /** + * Return the user unique name + */ + public function getUsername(); + + /** + * Return the user encoded password + */ + public function getPassword(); + + /** + * return the salt used to calculate the user password + */ + public function getSalt(); + + /** + * return the algorithm used to calculate the user password + */ + public function getAlgo(); + + /** + * Removes sensitive data from the user. + * + * This is important if, at any given point, sensitive information like + * the plain-text password is stored on this object. + * + * @return void + */ + public function eraseCredentials(); +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php b/core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php new file mode 100644 index 000000000..217e693c1 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/core/lib/Thelia/Core/Template/Element/LoopResultRow.php b/core/lib/Thelia/Core/Template/Element/LoopResultRow.php index 4a14521c7..73d2f937a 100755 --- a/core/lib/Thelia/Core/Template/Element/LoopResultRow.php +++ b/core/lib/Thelia/Core/Template/Element/LoopResultRow.php @@ -29,12 +29,12 @@ class LoopResultRow public function set($key, $value) { - $this->substitution["#".$key] = $value; + $this->substitution[$key] = $value; } public function get($key) { - return $this->substitution["#".$key]; + return $this->substitution[$key]; } public function getVarVal() @@ -42,4 +42,9 @@ class LoopResultRow return $this->substitution; } + public function getVars() + { + return array_keys($this->substitution); + } + } diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php index f79f98a1a..4418bd50c 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php @@ -35,7 +35,7 @@ class Assetic implements SmartyPluginInterface { $web_root = THELIA_WEB_DIR; - $asset_dir_from_web_root = '/assets/admin/default'; // FIXME + $asset_dir_from_web_root = 'assets/admin/default'; // FIXME $this->asset_manager = new SmartyAssetsManager($web_root, $asset_dir_from_web_root); } diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php index d1ef26244..51ff06e9c 100644 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php @@ -30,6 +30,30 @@ use Thelia\Core\Template\Smarty\SmartyPluginDescriptor; use Thelia\Core\Template\Smarty\SmartyPluginInterface; use Thelia\Log\Tlog; +/** + * + * Plugin for smarty defining blocks and functions for using Form display. + * + * blocks : + * - {form name="myForm"} ... {/form} => find form named myForm, + * create an instance and assign this instanciation into smarty variable. Form must be declare into + * config using tag + * + * - {form_field form=$form.fieldName} {/form_field} This block find info into the Form field containing by + * the form paramter. This field must be an instance of FormView. fieldName is the name of your field. This block + * can output these info : + * * $name => name of yout input + * * $value => value for your input + * * $label => label for your input + * * $error => boolean for know if there is error for this field + * * $attr => all your attribute for your input (define when you construct programmatically you form) + * + * - {form_error form=$form.fieldName} ... {/form_error} Display this block if there are errors on this field. + * fieldName is the name of your field + * + * Class Form + * @package Thelia\Core\Template\Smarty\Plugins + */ class Form implements SmartyPluginInterface { @@ -63,11 +87,7 @@ class Form implements SmartyPluginInterface throw new \InvalidArgumentException("Missing 'name' parameter in form arguments"); } - $form = BaseForm::getFormFactory($this->request); - $formBuilder = $form->createBuilder('form'); - $instance = $this->getInstance($params['name']); - $instance = $instance->buildForm($formBuilder, array()); $template->assign("form", $instance->getForm()->createView()); } else { @@ -179,8 +199,10 @@ class Form implements SmartyPluginInterface throw new ElementNotFoundException(sprintf("%s form does not exists", $name)); } + $class = new \ReflectionClass($this->formDefinition[$name]); - return new $this->formDefinition[$name]; + + return $class->newInstance($this->request); } /** diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php index e02db2726..18ae783f0 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php @@ -43,6 +43,9 @@ class TheliaLoop implements SmartyPluginInterface protected $dispatcher; + protected $loopstack = array(); + protected $varstack = array(); + public function __construct(Request $request, EventDispatcherInterface $dispatcher) { $this->request = $request; @@ -84,6 +87,10 @@ class TheliaLoop implements SmartyPluginInterface $name = $params['name']; if ($content === null) { + // Check if a loop with the same name exists in the current scope, and abort if it's the case. + if (array_key_exists($name, $this->varstack)) { + throw new \InvalidArgumentException("A loop named '$name' already exists in the current scope."); + } $loop = $this->createLoopInstance(strtolower($params['type'])); @@ -92,11 +99,10 @@ class TheliaLoop implements SmartyPluginInterface self::$pagination[$name] = null; $loopResults = $loop->exec(self::$pagination[$name]); + $this->loopstack[$name] = $loopResults; - $template->assignByRef($name, $loopResults); - - } else { - $loopResults = $template->getTemplateVars($name); + } else { + $loopResults = $this->loopstack[$name]; $loopResults->next(); } @@ -104,21 +110,50 @@ class TheliaLoop implements SmartyPluginInterface if ($loopResults->valid()) { $loopResultRow = $loopResults->current(); + // On first iteration, save variables that may be overwritten by this loop + if (! isset($this->varstack[$name])) { + + $saved_vars = array(); + + $varlist = $loopResultRow->getVars(); + $varlist[] = 'LOOP_COUNT'; + $varlist[] = 'LOOP_TOTAL'; + + foreach($varlist as $var) { + $saved_vars[$var] = $template->getTemplateVars($var); + } + + $this->varstack[$name] = $saved_vars; + } + foreach($loopResultRow->getVarVal() as $var => $val) { - $template->assign(substr($var, 1), $val); + $template->assign($var, $val); } - $template->assign('__COUNT__', 1 + $loopResults->key()); - $template->assign('__TOTAL__', $loopResults->getCount()); + // Assign meta information + $template->assign('LOOP_COUNT', 1 + $loopResults->key()); + $template->assign('LOOP_TOTAL', $loopResults->getCount()); - //$repeat = $loopResults->valid(); $repeat = true; } + // Loop is terminated. Cleanup. + if (! $repeat) { + // Restore previous variables values before terminating + if (isset($this->varstack[$name])) { + foreach($this->varstack[$name] as $var => $value) { + $template->assign($var, $value); + } + + unset($this->varstack[$name]); + } + } if ($content !== null) { + if ($loopResults->isEmpty()) { + $content = ""; + } - if ($loopResults->isEmpty()) $content = ""; return $content; } } @@ -177,10 +212,10 @@ class TheliaLoop implements SmartyPluginInterface $loopName = $params['rel']; // Find loop results in the current template vars - $loopResults = $template->getTemplateVars($loopName); + /* $loopResults = $template->getTemplateVars($loopName); if (empty($loopResults)) { throw new \InvalidArgumentException("Loop $loopName is not defined."); - } + }*/ // Find pagination $pagination = self::getPagination($loopName); @@ -224,14 +259,11 @@ class TheliaLoop implements SmartyPluginInterface $loopName = $params['rel']; - // Find loop results in the current template vars - $loopResults = $template->getTemplateVars($loopName); - - if (empty($loopResults)) { + if (! isset($this->loopstack[$loopName])) { throw new \InvalidArgumentException("Loop $loopName is not defined."); } - return $loopResults->isEmpty(); + return $this->loopstack[$loopName]->isEmpty(); } /** @@ -307,9 +339,7 @@ class TheliaLoop implements SmartyPluginInterface $value = (string)$argument->default; } - $test = $argument->type->getFormatedValue($value); - - $loop->{$argument->name} = $value === null ? null : $test; + $loop->{$argument->name} = $value === null ? null : $argument->type->getFormatedValue($value); } if (!empty($faultActor)) { diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php index 403cfac80..ec984613e 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php @@ -44,7 +44,7 @@ class Translation implements SmartyPluginInterface } // TODO - return "[$string]"; + return "$string"; } /** diff --git a/core/lib/Thelia/Form/AdminLogin.php b/core/lib/Thelia/Form/AdminLogin.php index 2bd2debac..32a8e0255 100644 --- a/core/lib/Thelia/Form/AdminLogin.php +++ b/core/lib/Thelia/Form/AdminLogin.php @@ -23,32 +23,28 @@ namespace Thelia\Form; -use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\Choice; -class AdminLogin extends AbstractType { +class AdminLogin extends BaseForm { - public function buildForm(FormBuilderInterface $builder, array $options) + protected function buildForm() { - return $builder + $this->form ->add("username", "text", array( "constraints" => array( new NotBlank(), new Length(array("min" => 3)) ) )) - ->add("password", "password"); + ->add("password", "password", array( + "constraints" => array( + new NotBlank() + ) + )) + ->add("remember_me", "checkbox"); } - /** - * Returns the name of this type. - * - * @return string The name of this type - */ - public function getName() - { - return "admin_login"; - } } \ No newline at end of file diff --git a/core/lib/Thelia/Form/BaseForm.php b/core/lib/Thelia/Form/BaseForm.php index da6689f24..02f86fe02 100644 --- a/core/lib/Thelia/Form/BaseForm.php +++ b/core/lib/Thelia/Form/BaseForm.php @@ -32,30 +32,43 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider; use Symfony\Component\Validator\Validation; use Thelia\Model\ConfigQuery; -class BaseForm { - +abstract class BaseForm { /** - * @param Request $request - * @return \Symfony\Component\Form\FormFactoryInterface + * @var \Symfony\Component\Form\FormFactoryInterface */ - public static function getFormFactory(Request $request, $secret = null) + protected $form; + + public function __construct(Request $request, $type= "form", $data = array(), $options = array()) { $validator = Validation::createValidator(); - $form = Forms::createFormFactoryBuilder() + $this->form = Forms::createFormFactoryBuilder() ->addExtension(new HttpFoundationExtension()) ->addExtension( new CsrfExtension( new SessionCsrfProvider( $request->getSession(), - $secret ?: ConfigQuery::read("form.secret", md5(__DIR__)) + isset($option["secret"]) ? $option["secret"] : ConfigQuery::read("form.secret", md5(__DIR__)) ) ) ) ->addExtension(new ValidatorExtension($validator)) - ->getFormFactory(); + ->getFormFactory() + ->createBuilder($type, $data, $options); + ; - return $form; + $this->buildForm(); } + + /** + * @return \Symfony\Component\Form\Form + */ + public function getForm() + { + return $this->form->getForm(); + } + + abstract protected function buildForm(); + } diff --git a/core/lib/Thelia/Form/CustomerCreation.php b/core/lib/Thelia/Form/CustomerCreation.php index 120be2f41..68726356d 100644 --- a/core/lib/Thelia/Form/CustomerCreation.php +++ b/core/lib/Thelia/Form/CustomerCreation.php @@ -24,31 +24,25 @@ namespace Thelia\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Constraints\NotBlank; -class CustomerCreation extends AbstractType +class CustomerCreation extends BaseForm { - public function buildForm(FormBuilderInterface $builder, array $options) + protected function buildForm() { - return $builder->add("name", "text") + $this->form->add("name", "text") ->add("email", "email", array( "attr" => array( "class" => "field" ), - "label" => "email" + "label" => "email", + "constraints" => array( + new NotBlank() + ) ) ) ->add('age', 'integer'); } - - /** - * Returns the name of this type. - * - * @return string The name of this type - */ - public function getName() - { - return "customer creation"; - } } \ No newline at end of file diff --git a/core/lib/Thelia/Model/Admin.php b/core/lib/Thelia/Model/Admin.php index 854ccb068..68b151193 100755 --- a/core/lib/Thelia/Model/Admin.php +++ b/core/lib/Thelia/Model/Admin.php @@ -3,7 +3,7 @@ namespace Thelia\Model; use Thelia\Model\om\BaseAdmin; - +use Symfony\Component\Security\Core\User\UserInterface; /** * Skeleton subclass for representing a row from the 'admin' table. @@ -16,6 +16,27 @@ use Thelia\Model\om\BaseAdmin; * * @package propel.generator.Thelia.Model */ -class Admin extends BaseAdmin +class Admin extends BaseAdmin implements UserInterface { + /** + * {@inheritDoc} + */ + public function getUsername() { + return $this->getLogin(); + } + + /** + * {@inheritDoc} + */ + public function eraseCredentials() { + $this->setPassword(null); + } + + /** + * {@inheritDoc} + */ + public function getRoles() { + return array(new Role('USER_CUSTOMER')); + } + } diff --git a/core/lib/Thelia/Model/Customer.php b/core/lib/Thelia/Model/Customer.php index 835f91e59..1002d8d94 100755 --- a/core/lib/Thelia/Model/Customer.php +++ b/core/lib/Thelia/Model/Customer.php @@ -3,6 +3,8 @@ namespace Thelia\Model; use Thelia\Model\om\BaseCustomer; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\Role\Role; /** @@ -16,6 +18,29 @@ use Thelia\Model\om\BaseCustomer; * * @package propel.generator.Thelia.Model */ -class Customer extends BaseCustomer +class Customer extends BaseCustomer implements UserInterface { + /** + * {@inheritDoc} + */ + + public function getUsername() { + return $this->getEmail(); + } + + /** + * {@inheritDoc} + */ + public function eraseCredentials() { + $this->setPassword(null); + } + + /** + * {@inheritDoc} + */ + public function getRoles() { + return array(new Role('USER_CUSTOMER')); + } } + + diff --git a/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php b/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php new file mode 100644 index 000000000..7590b3788 --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php @@ -0,0 +1,75 @@ +encode('password', 'sha512', 'a simple salt'); + + // echo "PASS=\{$pass\}"; + + $this->assertEquals("L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ==", $pass, "Expected password not found."); + } + + public function testIsEqual() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertTrue($encoder->isEqual($exp, 'password', 'sha512', 'a simple salt')); + } + + public function testWrongPass() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertFalse($encoder->isEqual($exp, 'grongron', 'sha512', 'a simple salt')); + } + + public function testWrongSalt() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertFalse($encoder->isEqual($exp, 'password', 'sha512', 'another salt')); + } + + public function testWrongAlgo() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertFalse($encoder->isEqual($exp, 'password', 'md5', 'another salt')); + } + + /** + * @expectedException LogicException + */ + public function testUnsupportedAlgo() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $encoder->isEqual($exp, 'password', 'sbonk', 'another salt'); + } + + /** + * @expectedException LogicException + */ + public function testEncodeWrongAlgorithm() + { + $encoder = new PasswordHashEncoder(); + + $encoder->encode('password', 'pouët', 'a simple salt'); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php b/core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php new file mode 100644 index 000000000..84cb0d1ef --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php @@ -0,0 +1,49 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Security; + +use Thelia\Core\Security\SecurityManager; +/** + * + * @author Franck Allimant + * + */ +class SecurityManagerTest extends \PHPUnit_Framework_TestCase +{ + public function testGetSetToken() + { + /* + $context = new SecurityManager($authProvider)( + $this->getMock('AuthenticationProviderInterface'), + $this->getMock('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface') + ); + $this->assertNull($context->getToken()); + + $context->setToken($token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')); + $this->assertSame($token, $context->getToken()); + */ + // $this->assertFalse(1==1, "faux !"); + } +} diff --git a/core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php b/core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php new file mode 100644 index 000000000..147c5b816 --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php @@ -0,0 +1,38 @@ +assertFalse($token->isAuthenticated()); + + $token = new UsernamePasswordToken('username', 'password', true); + $this->assertTrue($token->isAuthenticated()); + } + + /** + * @expectedException LogicException + */ + public function testSetAuthenticatedToTrue() + { + $token = new UsernamePasswordToken('foo', 'bar', true); + $token->setAuthenticated(true); + } + + public function testSetAuthenticatedToFalse() + { + $token = new UsernamePasswordToken('foo', 'bar', true); + $token->setAuthenticated(false); + $this->assertFalse($token->isAuthenticated()); + } + + public function testEraseCredentials() + { + $token = new UsernamePasswordToken('foo', 'bar', true); + $token->eraseCredentials(); + $this->assertEquals('', $token->getCredentials()); + } +} \ No newline at end of file diff --git a/templates/admin/default/login.html b/templates/admin/default/login.html index 5c33d742e..20fadb19b 100755 --- a/templates/admin/default/login.html +++ b/templates/admin/default/login.html @@ -2,7 +2,7 @@ {include file='includes/header.inc.html'}
- + {{intl l='abcd'}|capitalize} @@ -15,7 +15,7 @@

{intl l='Thelia Back Office'}

-
+ {form_field_hidden form=$form} {form_field form=$form.username} {form_error form=$form.username} @@ -27,7 +27,9 @@ {/form_field} + {form_field form=$form.remember_me} + {/form_field}
diff --git a/templates/smarty-sample/category.html b/templates/smarty-sample/category.html index c6c70c69a..14c4757d6 100755 --- a/templates/smarty-sample/category.html +++ b/templates/smarty-sample/category.html @@ -1,8 +1,39 @@ +{include file="included.html"} + {loop name="category0" type="category" parent="0"} -

1 - CATEGORY : #TITLE

-

+

Out before - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

{loop name="category1" type="category" parent="#ID"} -

2 - SUBCATEGORY : #TITLE

+

Inner - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

{/loop} -

1bis - CATEGORY : #TITLE

-{/loop} \ No newline at end of file + + {#myid=#ID} + + {loop name="category2" type="category" parent="#ID"} +

Inner 2 before - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ + {loop name="category3" type="category" parent="#myid"} +

Inner inner 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + +

Inner 2 after - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + +

Out after - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+
+ + {ifloop rel="category2"} +

Hey, y'a d'la categorie 2 !

+ {/ifloop} + + {elseloop rel="category2"} +

Hey, y'a PAS de categorie 2 !

+ {/elseloop} + + {loop name="category2" type="category" parent="#myid"} +

Exter 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} +{/loop} + +{loop name="category2" type="category" parent="1"} +

Final Exter 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+{/loop} diff --git a/templates/smarty-sample/included.html b/templates/smarty-sample/included.html new file mode 100644 index 000000000..93581ff7f --- /dev/null +++ b/templates/smarty-sample/included.html @@ -0,0 +1,12 @@ +{loop name="included0" type="category" parent="0"} +

Out before - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {loop name="category1" type="category" parent="#ID"} +

Inner - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + + {loop name="category2" type="category" parent="#ID"} +

Inner 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} +

Out after - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+
+{/loop} \ No newline at end of file diff --git a/templates/smarty-sample/index.html b/templates/smarty-sample/index.html index 57cbd928a..24f964510 100755 --- a/templates/smarty-sample/index.html +++ b/templates/smarty-sample/index.html @@ -8,7 +8,7 @@ An image from asset directory : {intl l='An internationalized string'}
{form name="thelia.customer.creation"} -
+ {form_field_hidden form=$form} {form_field form=$form.email} @@ -21,6 +21,7 @@ An image from asset directory : {form_field form=$form.age} {intl l='age'} : {/form_field} +
{/form}