So i'm trying to use the HWI-OAuthBundle and FoS-UserBundle in order to enable users to connect from our schools credentials. So I applied what was said in the docs and configured the custom resource owner.
When i try to login, i'm correctly redirected to the schools OAuth service where i login & authorize the client to access my profile but then i have this error message :
No property defined for entity for resource owner 'myecp'.
I tried several solutions I found on internet, none of them works for me.
Here is my code :
config.yml
#HWIOAuthBundle
hwi_oauth:
connect:
account_connector: my.myecp.user_provider
firewall_names: [secured_area]
fosub:
username_iterations: 30
properties:
myecp: myecp_id
resource_owners:
myecp:
type: oauth2
client_id: "%myecp_client_id%"
client_secret: "%myecp_secret%"
access_token_url: https://my.ecp.fr/oauth/v2/token
authorization_url: https://my.ecp.fr/oauth/v2/auth
infos_url: https://my.ecp.fr/api/v1/members/me
user_response_class: HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse
paths:
identifier: id
nickname: login
realname: [first_name, last_name]
mail: mail
options:
csrf: true
#FOSUserBundle
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\Personnes
from_email:
address: "%mailer_user%"
sender_name: "%mailer_user%"
#Services
services:
my.myecp.user_provider:
class: AppBundle\Security\Core\User\FOSUBPersonnesProvider
arguments: ['#fos_user.user_manager', { myecp: myecp_id }]
security.yml :
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/security.html
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
# http://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
fos_userbundle:
id: fos_user.user_provider.username
# in_memory:
# memory: ~
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
anonymous: ~
oauth:
resource_owners:
myecp: "/login/check"
login_path: /login
use_forward: false
failure_path: /login
oauth_user_provider:
service: my.oauth_aware.user_provider.service
main:
# anonymous: ~
# activate different ways to authenticate
# http_basic: ~
# http://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: ~
# http://symfony.com/doc/current/cookbook/security/form_login_setup.html
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
access_control:
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
services.yml:
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["#another_service_name", "plain_value", "%parameter_name%"]
my.oauth_aware.user_provider.service:
class: HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider
arguments:
- '#fos_user.user_manager'
- ['pass properties as array']
routing.yml:
app:
resource: "#AppBundle/Controller/"
type: annotation
hwi_oauth_redirect:
resource: "#HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
hwi_oauth_connect:
resource: "#HWIOAuthBundle/Resources/config/routing/connect.xml"
prefix: /connect
hwi_oauth_login:
resource: "#HWIOAuthBundle/Resources/config/routing/login.xml"
prefix: /login
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
myecp_login:
path: /login/check
logout:
path: /logout
FOSUBPersonnesProvider.php
class FOSUBPersonnesProvider extends BaseFOSUBProvider
{
/**
* {#inheritDoc}
*/
public function connect(UserInterface $user, UserResponseInterface $response)
{
// get property from provider configuration by provider name
// , it will return `myecp_id` in that case (see service definition below)
$property = $this->getProperty($response);
$username = $response->getUsername(); // get the unique user identifier
//on connect - get the access token and the user ID
$service = $response->getResourceOwner()->getName();
$setter = 'set'.ucfirst($service);
$setter_id = $setter.'Id';
$setter_token = $setter.'AccessToken';
//we "disconnect" previously connected users
$existingUser = $this->userManager->findUserBy(array($property => $username));
if (null !== $existingUser) {
$existingUser->$setter_id(null);
$existingUser->$setter_token(null);
$this->userManager->updateUser($existingUser);
}
$user->$setter_id($username);
$user->$setter_token($response->getAccessToken());
$this->userManager->updateUser($user);
}
/**
* {#inheritdoc}
*/
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$userId = $response->getUsername();
$user = $this->userManager->findUserBy(array('myecpId' => $userId));
// if null just create new user and set it properties
if (null === $user) {
$first_name = $response->getFirstName();
$last_name = $response->getLastName();
$email = $response->getEmail();
$service = $response->getResourceOwner()->getName();
$setter = 'set'.ucfirst($service);
$setter_id = $setter.'Id';
$setter_token = $setter.'AccessToken';
// create new user here
$user = $this->userManager->createUser();
$user->setPrenom($first_name);
$user->setNom($last_name);
$user->setMail($email);
$user->$setter_id($userId);
$user->$setter_token($response->getAccessToken());
$this->userManager->updateUser($user);
return $user;
}
// else update access token of existing user
$user = parent::loadUserByOAuthUserResponse($response);
$serviceName = $response->getResourceOwner()->getName();
$setter = 'set' . ucfirst($serviceName) . 'AccessToken';
$user->$setter($response->getAccessToken());//update access token
return $user;
}
}
Thanks for your help !
First change in file services.yml line where You pass argument for user.provider.
For example change last line, below is my working example for google.
my.custom.user_provider:
class: YOURBUNDLENAME\Security\Core\MyFOSUBUserProvider
arguments:
- '#fos_user.user_manager'
- arguments: **[ #fos_user.user_manager, { google: googleID }** ]
Next change $property to name of User entity property name ( myecp ?? ) . In my exmaple its googleID. $property is in FOSUBPersonnesProvider.php below connect() method.
My User.php entity looks like:
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="google_id", type="string", nullable=true)
*/
private $googleID;
I hope this will help you
I solved this problem.
Because here passing arguments as array:
arguments: ['#fos_user.user_manager', { myecp: myecp_id }] //In Service
Change to :
arguments: ['#fos_user.user_manager', myecp: myecp_id ]
When send second parameter in FOSUserProvider merging array HERE
Then ERROR message showing HERE
protected function getProperty(UserResponseInterface $response)
{
$resourceOwnerName = $response->getResourceOwner()->getName();
if (!isset($this->properties[$resourceOwnerName])) {
throw new \RuntimeException(sprintf("No property defined for entity for resource owner '%s'.", $resourceOwnerName));
}
return $this->properties[$resourceOwnerName];
}
If you are using Facebook:
arguments: ['#fos_user.user_manager', facebook: facebook ]
If you are using amazon:
arguments: ['#fos_user.user_manager', amazon: amazon ]
If you are using Odnoklassniki:
arguments: ['#fos_user.user_manager', odnoklassniki: odnoklassniki ]
If you are using github:
arguments: ['#fos_user.user_manager', github: github ]
If you are using google:
arguments: ['#fos_user.user_manager', google: google ]
Related
I am integrating lexik/jwtautheticationbundle version 1.3 with symfony 2.8 due to old application changes.
I have managed integrate and generate JWT authorization token but I wanted to use cookie and authentication_listener in lexit_jwt and I used but it has no any effect. If I use cookie, token should be saved in cookie but it is saved in session.
Can anyone suggest me why cookie enabled not working ?
Security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
providers:
db_provider:
entity:
class: AppBundle:User
property: username
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
provider: db_provider
form_login:
check_path: /api/login_check
username_parameter: username
password_parameter: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
api:
pattern: ^/api
stateless: true
anonymous: true
provider: db_provider
lexik_jwt:
authentication_listener: storefront.listener.jwt_authentication
cookie:
enabled: true
name: IDENTITY
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
services.yml
# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
#parameter_name: value
services:
#service_name:
# class: AppBundle\Directory\ClassName
# arguments: ['#another_service_name', 'plain_value', '%parameter_name%']
storefront.listener.jwt_authentication:
class: AppBundle\Listener\AuthenticationListener
arguments:
- "#security.token_storage"
- "#security.authentication.manager"
- []
AuthenicationListener.php
<?php
namespace AppBundle\Listener;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationListener extends JWTListener
{
public function handle(GetResponseEvent $event): void
{
if (!($requestToken = $this->getRequestToken($event->getRequest()))) {
return;
}
$token = new JWTUserToken();
$token->setRawToken($requestToken);
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authToken);
return;
} catch (AuthenticationException $failed) {
if ($this->config['throw_exceptions']) {
throw $failed;
}
}
}
}
I thought when cookie is enabled, it will save the token in cookie in the browser, but it meant to just read token from cookie only. So i figured myself. Thank you anyway
I'm new in symfony and I'm following the login form tutorial(https://symfony.com/doc/2.8/cookbook/security/form_login_setup.html) but I get the following error:
Uncaught PHP Exception LogicException: "The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?" at /var/www/html/app/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php line 157
Context: { "exception": "Object(LogicException)" }
I use the same SecurityController:
class SecurityController extends Controller{
* #Route("/admin", name="login")
public function loginAction(Request $request)
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
security.yml
# To get started with security, check out the documentation:
http://symfony.com/doc/current/book/security.html
security:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
app_users:
ldap:
service: app.ldap
base_dn: DC=corp, DC=int
search_dn: null
search_password: null
filter: (uid={username})
default_roles: ROLE_USER
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
# activate different ways to authenticate
# http_basic: ~
# http://symfony.com/doc/current/book/security.html#a-configuring-how-your-users-will-authenticate
# form_login: ~
# http://symfony.com/doc/current/cookbook/security/form_login_setup.html
secure:
provider: app_users
form_login_ldap:
service: app.ldap
dn_string: "uid={username},ou=Users,dc=corp,dc=int"
check_path: login_check
login_path: admin
logout: true
In your security.yml, try to replace
check_path: login_check
by
check_path: /login_check
And make your method like this :
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
}
I have a Symfony 2.7.6 project with custom Simple Form authentication provider and support for remember me functionality as well as impersonalization feature. Everything works as expected.
However, I want to introduce another authentication provider that will allow requests regardless of session state using two HTTP headers for authentication (e.g. API-Client-Id and API-Client-Token) for third-party applications.
I've created a Simple Pre-Auth authentication provider that validates these header fields and creates authentication token with empty User instance on success.
However, it looks like Symfony is trying to remember those API authentications using session, so I'm getting the following error on the second request: "You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.".
I can set stateless: true flag in my firewall configuration to disable session support, but it will disable it for both auth providers.
SO, how do I preserve existing functionality with my Simple Form authenticator and yet create another layer of authentication to be used for single stateless API requests?
I'm not sure if my approach is conceptually correct. I will gladly accept any suggestions and will provide any relevant information on first request.
Here's my security.yml config:
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
form_login:
login_path: app.login
check_path: app.session.sign_in
username_parameter: username
password_parameter: password
success_handler: app.security.login_handler
failure_handler: app.security.login_handler
require_previous_session: false
logout:
path: app.session.sign_out
invalidate_session: false
success_handler: app.security.logout_success_handler
# Simple form auth provider
simple_form:
authenticator: app.security.authenticator.out_service
# Token provider
simple_preauth:
authenticator: app.security.authenticator.api_client
remember_me:
name: "%app.session.remember_me.name%"
key: "%secret%"
lifetime: 1209600 # 14 days
path: /
domain: ~
always_remember_me: true
switch_user: { role: ROLE_ADMIN }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/recover-password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /, roles: IS_AUTHENTICATED_REMEMBERED }
providers:
main:
entity:
class: App\AppBundle\Model\User
property: id
encoders:
App\AppBundle\Model\User: plaintext
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_ACTIVE]
ROLE_API_CLIENT: ~
ROLE_USER: ~
ROLE_ACTIVE: ~
ApiClientAuthenticator.php:
<?php
namespace App\AppBundle\Security;
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\AppBundle\Model\User;
class ApiClientAuthenticator implements SimplePreAuthenticatorInterface
{
/** #var LoggerInterface */
protected $logger;
/** #var array */
protected $clients;
/**
* #param array $clients
*/
public function __construct(array $clients)
{
$this->clients = $clients;
}
public function createToken(Request $request, $providerKey)
{
$clientId = $request->headers->get('Api-Client-Id');
$clientSecret = $request->headers->get('Api-Client-Secret');
if (!$clientId || !$clientSecret) {
return null;
}
return new PreAuthenticatedToken(
'anon.',
[$clientId, $clientSecret],
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
list ($clientId, $clientSecret) = $token->getCredentials();
$foundClient = null;
foreach ($this->clients as $client) {
if ($client['id'] == $clientId) {
if ($client['secret'] == $clientSecret) {
$foundClient = $client;
break;
}
}
}
if (!$foundClient) {
throw new AuthenticationException;
}
$user = new User;
$user->setApiClient(true);
return new PreAuthenticatedToken(
$user,
$foundClient,
$providerKey,
['ROLE_API_CLIENT']
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return ($token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey);
}
}
I'm developing a RESTful web service in Symfony2 with FOSRest and FOSOauthServer bundles (... and many others). My problem is that with an access token of other user, the api gives response instead of a 403 status code. For example:
I have two users stored on database
userA with tokenA
userB with tokenB
Example Request
http://example.com/api/v1/userA/products?access_token=tokenB
Current Response
{
products: {
0: { ... }
1: { ... }
}
}
But I'm requesting products of user A with an access token of user B. How could I check if access token provided is of the products' owner??
My security.yml file:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
MY_ROLE:
# ...
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
SONATA:
- ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
anonymous: true
# FOSOAuthBundle and FOSRestBundle
oauth_token:
pattern: ^/oauth/v2/token
security: false
# oauth_authorize: commented because there are not oauth login form on this app
# pattern: ^/oauth/v2/auth
# Add your favorite authentication process here
api:
pattern: ^/api
fos_oauth: true
stateless: true
anonymous: false
# This firewall is used to handle the public login area
# This part is handled by the FOS User Bundle
main:
# ...
access_control:
# ...
# API (FOSRestBundle and FOSOAuthBundle)
- { path: ^/api, roles: [IS_AUTHENTICATED_FULLY] }
My routing.yml on ApiBundle
# API Endpoints
app_api_user_get_products:
pattern: /{username}/products
defaults: { _controller: ApiBundle:User:getProducts, _format: json }
methods: GET
My UserController.php
<?php
namespace App\ApiBundle\Controller;
Use App\MainBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ... more use statments
class UserController extends ApiController {
/**
* List user's products.
*
* #ApiDoc(
* resource = true,
* description="This method must have the access_token parameter. The parameters limit and offset are optional.",
* filters={
* {"name"="access_token", "dataType"="string", "required"="true"},
* {"name"="offset", "dataType"="integer", "default"="0", "required"="false"},
* {"name"="limit", "dataType"="integer", "default"="250", "required"="false"}
* },
* )
*
* #Annotations\QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing products.")
* #Annotations\QueryParam(name="limit", requirements="\d+", default="500", description="How many products to return.")
*
* #Annotations\View()
*
* #param User $user the request object
* #param ParamFetcherInterface $paramFetcher param fetcher service
*
* #return array
*/
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {
// $offset = $paramFetcher->get('offset');
// $offset = null == $offset ? 0 : $offset;
// $limit = $paramFetcher->get('limit');
try {
// estructure and exclude fields strategy http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies
$data = array('products' => array());
foreach ($user->getCatalog() as $p) {
if ($p->getAvailable() == true) {
$product = $p->getProduct();
$data['products'][] = array(
'id' => $product->getId(),
'reference' => $product->getReference(),
'brand' => $product->getBrand(),
'description' => $product->getDescription(),
// ...
);
}
}
} catch (\Exception $e) {
throw new HttpException(Codes::HTTP_INTERNAL_SERVER_ERROR, $e->getTraceAsString());
}
// New view
$view = new View($data);
$view->setFormat('json');
return $this->handleView($view);
}
}
Thank you very much for the help!
I've found the solution. It's easy, just I've added the following code in my rest controller and the configuration parameters on app/config.yml
UserController.php
...
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {
// Check if the access_token belongs to the user making the request
$requestingUser = $this->get('security.context')->getToken()->getUser();
if (!$requestingUser || $requestingUser !== $user) {
throw new AccessDeniedHttpException();
}
...
~/app/config.yml
# FOSRestBundle
fos_rest:
routing_loader:
default_format: json
param_fetcher_listener: true
view:
view_response_listener: force
access_denied_listener: # I've added this
# all requests using the 'json' format will return a 403 on an access denied violation
json: true
You can also make it simpler using #Security annotation in Symfony >= 2.4 . In your case it'll look like
/**
* #Security("user.getId() == userWithProducts.getId()")
*/
and the action header:
...
public function getProductsAction(User $userWithProducts, ParamFetcherInterface $paramFetcher, Request $request) {
...
I am trying to configure remember me feature of symfony using their default mechanism as described here; but couldn't make it work.
Cookie named REMEMBERME is created, but is set to deleted and its expire date is 1970. This is why I suppose remember me function is not working. However, when I use (always_remember_me: true) in security.yml, the code works nicely but it doesn't suite my purpose. Using (always_remember_me: true) even if user doesn't check REMEMBER ME checkbox in UI the cookie gets created.
Any help is highly appreciated
I am using the version 2.3.7
This is my complete security.yml file:
security:
firewalls:
main:
pattern: ^/
anonymous: ~
remember_me:
key: "%secret%"
lifetime: 31536000 # a year
domain: ~
path: /
remember_me_parameter: _remember_me
#always_remember_me: true
form_login:
login_path: _my_login
check_path: _my_login_check
always_use_default_target_path: true
default_target_path: /out/homepage
remember_me: true
logout:
path: _my_logout
target: _my_login
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_REMEMBERED }
providers:
chain_provider:
chain:
providers: [in_memory, user_db]
in_memory:
memory:
users:
user: { password: user, roles: 'ROLE_USER' }
admin: { password: admin, roles: ['ROLE_ADMIN', 'ROLE_USER'] }
user_db:
entity: {class: ProjectName\StoreBundle\Entity\User, property: username}
encoders:
ProjectName\StoreBundle\Entity\User:
algorithm: sha1
iterations: 1
encode_as_base64: false
Symfony\Component\Security\Core\User\User: plaintext
LoginController.php
class LoginController extends Controller
{
/**
* #Route("/login", name="_my_login")
* #Template()
*/
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// get the login error if there is one
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(
SecurityContext::AUTHENTICATION_ERROR
);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return array(
// last username entered by the user
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
);
}
/**
* #Route("/login_check", name="_my_login_check")
*/
public function loginCheckAction()
{
}
I added the value attribute inside the input tag. After removing it, it worked for now. :)
<input type="checkbox" id="remember_me" name="_remember_me" checked /> Remember me
I suppose the checkbox is named incorrectly. It should have the 'name' attribute set to '_remember_me' to make the magic happen. Can you post the content of the form template?