Closed lukeman83 closed 11 years ago
Can you elaborate a bit more what the issue is?
I would to use Restful Api from native mobile apps. (without browser and cookie) I thought to authenticate users by wsse and give logged user in restcontroller. Now I have two problems: 1)If I'm not logged the API don't respond. 2)Which is the way to get logged user in RestController using wsse authentication?
@lukeman83 It is possible. You need to create your own security provider which authenticate the user over WSSE. Then you must configure the firewall to use you own security provider. Maybe one of this bundles can help you.
would be great if someone could donate a doc chapter on this.
I used a FOSRestBundleByExample https://github.com/sdiaz/FOSRestBundleByExample/blob/master/app/Resources/doc/index.md to integrate FOSRestBundle and MopaWSSEAuthenticationBundle. My own security provider is done! But I want to retrieve the logged user in a controller and I want to use the Api without classic login but with wsse authentication only.
Retrieve the current user
To retrieve the current user then you must inject the security.context
service in your controller. Now you can do this:
$user = $context->getToken()->getUser();
Use WSSE only
Make sure your firewall is in the right order. The first firewall wich matched the request uri will be used (WSSE, Login, ...). If you want to disable the form login completly change the pattern option in security.yml
.
Is this the right order?
firewalls:
wsse_secured:
pattern: ^/api/.*
stateless: true
wsse:
nonce_dir: null
lifetime: 5184000
provider: fos_userbundle
anonymous: true
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
check_path: fos_user_security_check
login_path: fos_user_security_login
default_target_path: homepage
fos_facebook:
app_url: %facebookAppUrl%
server_url: %facebookServerUrl%
login_path: fos_user_security_login
check_path: _security_check
default_target_path: homepage
provider: my_fos_facebook_provider
logout:
handlers: ["fos_facebook.logout_handler"]
anonymous: true
switch_user: true
oauth_token:
pattern: ^/oauth/v2/token
security: false
oauth_authorize:
pattern: ^/oauth/v2/auth
form_login:
provider: fos_userbundle
check_path: /oauth/v2/auth_login_check
login_path: /oauth/v2/auth_login
anonymous: true
pattern: ^/oauth/v2/auth
api:
pattern: ^/api
fos_oauth: true
stateless: true
@lukeman83 no it is not. the firewall pattern: ^/
should be the last one as it is a catch-all (any firewall placed after it can never been reached)
However, wsse_secured
and api
firewalls should be combined together as they have the same pattern (you cannot trigger 2 firewalls for the same url so api
can never be reached to authenticate on the API using OAuth).
And oauth_authorize
may not be necessary as authenticating for this url can be done by the main firewall (which is already the case currently btw because of the order)
It is ok now?
firewalls:
wsse_secured:
pattern: ^/api/.*
stateless: true
wsse:
nonce_dir: null
lifetime: 5184000
provider: fos_userbundle
anonymous: true
oauth_token:
pattern: ^/oauth/v2/token
security: false
oauth_authorize:
pattern: ^/oauth/v2/auth
form_login:
provider: fos_userbundle
anonymous: true
api:
pattern: ^/graph
fos_oauth: true
stateless: true
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
check_path: fos_user_security_check
login_path: fos_user_security_login
default_target_path: homepage
fos_facebook:
app_url: %facebookAppUrl%
server_url: %facebookServerUrl%
login_path: fos_user_security_login
check_path: _security_check
default_target_path: homepage
provider: my_fos_facebook_provider
logout:
handlers: ["fos_facebook.logout_handler"]
anonymous: true
switch_user: true
Or I have to combine wsse_secured
and api
together?
@lukeman83 You changed the pattern from the api
firewall in your second comment. What is now the right pattern, /graph
or /api
?
I have not use oauth pattern yet. I think I can you whatever pattern I want in the future.
At the moment the RESPONSE is: 403 Forbidden
And this is my token creator controller:
/**
* WSSE Token generation
*
* @return FOSView
* @throws AccessDeniedException
* @ApiDoc()
*/
public function postTokenCreateAction()
{
$view = FOSView::create();
$request = $this->getRequest();
$username = $request->get('_username');
$password = $request->get('_password');
//$csrfToken = $this->container->get('form.csrf_provider')->generateCsrfToken('authenticate');
//$data = array('csrf_token' => $csrfToken,);
$um = $this->get('fos_user.user_manager');
$user = $um->findUserByUsernameOrEmail($username);
if (!$user instanceof User) {
throw new AccessDeniedException("Wrong user");
}
$created = date('c');
$nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
$nonceHigh = base64_encode($nonce);
$passwordDigest = base64_encode(sha1($nonce . $created . $password . "{".$user->getSalt()."}", true));
$header = "UsernameToken Username=\"{$username}\", PasswordDigest=\"{$passwordDigest}\", Nonce=\"{$nonceHigh}\", Created=\"{$created}\"";
$view->setHeader("Authorization", 'WSSE profile="UsernameToken"');
$view->setHeader("X-WSSE", "UsernameToken Username=\"{$username}\", PasswordDigest=\"{$passwordDigest}\", Nonce=\"{$nonceHigh}\", Created=\"{$created}\"");
$data = array('WSSE' => $header);
$view->setStatusCode(200)->setData($data);
return $view;
}
Is your app online or opensource?
No it isn't.
Maybe I need to inject the security.context
service in my controller.,
Which is the best way to do it?
@stof what do you think about?
By the way...At the moment I have two problems:
-I need the right way to inject the security.context
service in above postTokenCreateAction()
-When I try to use the api I receive 403 Forbidden Response
injecting a service into an action is nothing doorsill with this bundle. either you use the base controller or some how else inject the container. alternatively you define your controller as a service and explicitly do the injection
Can you show me an example please?
this is basic Symfony2 stuff and covered well in the official docs.
Ok, I will read the official docs. And what about 403 Forbidden Response?
Is this the right way to define my controller as a service and explicitly do the injection?
wsse_security:
class: project\MainBundle\Controller\Api\projectApiSecurityController
arguments: ["@security.context"]
<?php
/**
* This file is part of the FOSRestByExample package.
*
* (c) Santiago Diaz <santiago.diaz@me.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace project\MainBundle\Controller\Api;
use project\UserBundle\Entity\User;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\Prefix;
use FOS\RestBundle\Controller\Annotations\NamePrefix;
use FOS\RestBundle\View\RouteRedirectView;
use FOS\RestBundle\View\View AS FOSView;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\SecurityContextInterface;
/**
* Controller that provides Restfuls security functions.
*
* @Prefix("/security")
* @NamePrefix("project_securityrest_")
* @author Santiago Diaz <santiago.diaz@me.com>
*/
class projectApiSecurityController extends Controller
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
/**
* WSSE Token generation
*
* @return FOSView
* @throws AccessDeniedException
* @ApiDoc()
*/
public function postTokenCreateAction()
{
$view = FOSView::create();
$request = $this->getRequest();
$username = $request->get('_username');
$password = $request->get('_password');
//$csrfToken = $this->container->get('form.csrf_provider')->generateCsrfToken('authenticate');
//$data = array('csrf_token' => $csrfToken,);
$um = $this->get('fos_user.user_manager');
$user = $um->findUserByUsernameOrEmail($username);
if (!$user instanceof User) {
throw new AccessDeniedException("Wrong user");
}
$created = date('c');
$nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
$nonceHigh = base64_encode($nonce);
$passwordDigest = base64_encode(sha1($nonce . $created . $password . "{".$user->getSalt()."}", true));
$header = "UsernameToken Username=\"{$username}\", PasswordDigest=\"{$passwordDigest}\", Nonce=\"{$nonceHigh}\", Created=\"{$created}\"";
$view->setHeader("Authorization", 'WSSE profile="UsernameToken"');
$view->setHeader("X-WSSE", "UsernameToken Username=\"{$username}\", PasswordDigest=\"{$passwordDigest}\", Nonce=\"{$nonceHigh}\", Created=\"{$created}\"");
$data = array('WSSE' => $header);
$view->setStatusCode(200)->setData($data);
return $view;
}
/**
* WSSE Token Remove
*
* @return FOSView
* @ApiDoc()
*/
public function getTokenDestroyAction()
{
$view = FOSView::create();
$security = $this->get('security.context');
$token = new AnonymousToken(null, new User());
$security->setToken($token);
$this->get('session')->invalidate();
$view->setStatusCode(200)->setData('Logout successful');
return $view;
}
}
Yes this is one method to inject the security.context
.
It doesn't work:
Catchable Fatal Error: Argument 1 passed to project\MainBundle\Controller\Api\projectApiSecurityController::__construct() must be an instance of project\MainBundle\Controller\Api\Symfony\Component\Security\Core\SecurityContextInterface, none given, called in C:\xampp\htdocs\project\app\cache\dev\jms_diextra\controller_injectors\projectMainBundleControllerApiprojectApiSecurityController.php on line 13 and defined in C:\xampp\htdocs\project\src\project\MainBundle\Controller\Api\projectApiSecurityController.php line 40
However, it is weird to extend the base controller class when defining it as a service as this base class is not meant to be used as a service. Btw, your service definition is wrong. It misses the call to setContainer
to initialize the base controller.
and when using your controller as service, you need to modify your routing to reference the controller as service (see the official doc)
you also need to ensure that your route is in fact using this service. i feel like you are jumping a head here. you really should try and learn the basics before getting to more advanced stuff.
a framework like Symfony2 requires a bit of studying before it can be used effectively.
Thanks! I read docs and my code is:
my routing.yml:
my_security:
class: project\MainBundle\Services\MySecurity
arguments: ["@security.context"]
my Security class:
namespace project\MainBundle\Services\MySecurity;
use Symfony\Component\Security\Core\SecurityContextInterface;
class MySecurity
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
}
in my controller:
$user=$this->container->get('my_security')->getToken()->getUser();
the error is:
he autoloader expected class \"project\MainBundle\Services\MySecurity\" to be defined in file \"C:\xampp\htdocs\project\/src\/\project\MainBundle\Services\MySecurity.php\". The file was found but the class was not in it, the class name or namespace probably has a typo.
your class in the file is project\MainBundle\Services\MySecurity\MySecurity
, not project\MainBundle\Services\MySecurity
because your naemspace is wrong (as said by the error message btw).
and if you want to get it from the container, you don't need to create a wrapper around security.context
. You can get it directly.
Do you mean I need to use?
$user=$this->container->get('security.context')->getToken()->getUser();
@lukeman83 Yes.
Ok, so I used it in my controller
$user=$this->container->get('security.context')->getToken()->getUser();
$id=$user->getId();
but the error is:
Call to a member function getId() on a non-object
Than you are not authenticated.
Ok.... So the last problem is authentication by wsse. I received 403 Forbidden Response.
You haven't the required privileges to visit this site. Maybe you are not authenticated?
Yes, but I don't understand what I need to change.
my security.yml is
imports:
- { resource: facebookParameters.ini }
jms_security_extra:
secure_all_services: false
expressions: true
security:
providers:
my_fos_facebook_provider:
id: my.facebook.user
fos_userbundle:
id: fos_user.user_manager
encoders:
FOS\UserBundle\Model\UserInterface: sha512
firewalls:
wsse_secured:
pattern: ^/api/.*
stateless: true
wsse:
nonce_dir: null
lifetime: 5184000
provider: fos_userbundle
anonymous: true
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
check_path: fos_user_security_check
login_path: fos_user_security_login
default_target_path: homepage
fos_facebook:
app_url: %facebookAppUrl%
server_url: %facebookServerUrl%
login_path: fos_user_security_login
check_path: _security_check
default_target_path: homepage
provider: my_fos_facebook_provider
logout:
handlers: ["fos_facebook.logout_handler"]
anonymous: true
switch_user: true
access_control:
- { path: ^/$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/mobile, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login_api, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/security, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/_, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/reset, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/send-email, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/.*, role: ROLE_USER }
- { path: ^/admin/, role: ROLE_ADMIN }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
my config.yml is
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: facebookParameters.ini }
- { resource: websiteParameters.ini }
services:
my.facebook.user:
class: project\UserBundle\Security\User\Provider\FacebookProvider
arguments:
facebook: "@fos_facebook.api"
userManager: "@fos_user.user_manager"
validator: "@validator"
container: "@service_container"
....
framework:
#esi: ~
translator: { fallback: %locale% }
secret: %secret%
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: %kernel.debug%
form: true
csrf_protection: true
validation: { enable_annotations: true }
templating: { engines: ['twig'] } #assets_version: SomeVersionScheme
default_locale: %locale%
trust_proxy_headers: false # Should Request object should trust proxy headers (X_FORWARDED_FOR/HTTP_CLIENT_IP)
session: ~
.....
# Assetic Configuration
assetic:
debug: %kernel.debug%
use_controller: false
bundles: [ ]
#java: /usr/bin/java
filters:
cssrewrite: ~
...
fos_user:
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
firewall_name: wsse_secured #fos_secured
user_class: project\UserBundle\Entity\User
registration:
form:
type: project_user_registration
confirmation:
enabled: true
from_email:
address: %mailer_user%
sender_name: %sender_name%
fos_facebook:
file: %kernel.root_dir%/../vendor/facebook/php-sdk/src/base_facebook.php
alias: facebook
app_id: %facebookAppId%
secret: %facebookAppSecret%
cookie: true
permissions: [email, user_birthday, user_location, user_hometown, read_friendlists, user_relationships]
jms_security_extra:
secure_all_services: false
enable_iddqd_attribute: false
expressions: true
sensio_framework_extra:
router: { annotations: true }
request: { converters: true }
view: { annotations: false } # More info at https://github.com/FriendsOfSymfony/FOSRestBundle/issues/95
cache: { annotations: true }
jms_aop:
cache_dir: %kernel.cache_dir%/jms_aop
fos_rest:
view:
view_response_listener: true
failed_validation: HTTP_BAD_REQUEST
default_engine: php
formats:
json: true
xml: true
rss: false
format_listener:
prefer_extension: true
body_listener:
decoders:
json: fos_rest.decoder.json
param_fetcher_listener: true
allowed_methods_listener: true
# Mopa Rackspace Cloud Files configuration
mopa_wsse_authentication:
provider_class: Mopa\Bundle\WSSEAuthenticationBundle\Security\Authentication\Provider\WsseAuthenticationProvider
listener_class: Mopa\Bundle\WSSEAuthenticationBundle\Security\Firewall\WsseListener
factory_class: Mopa\Bundle\WSSEAuthenticationBundle\Security\Factory\WsseFactory
I'm testing it with this Dev HTTP Client extension (https://chrome.google.com/webstore/detail/aejoelaoggembcahagimdiliamlcdmfm) and the Checking the Restful API section (of https://github.com/sdiaz/FOSRestBundleByExample/blob/master/app/Resources/doc/index.md)
Well, maybe you are anonymous. Your firewall allows it. And in such case, $user
will be a string.
Ok I changed firewall properties:
firewalls:
wsse_secured:
pattern: ^/api/.*
stateless: true
wsse:
nonce_dir: null
lifetime: 5184000
provider: fos_userbundle
anonymous: false
I try to use my api in this way:
First I do a POST request:
localhost/project/web/app_dev.php/security/token/create?_username=a@a.it&_password=aaa
HEADERS:
Accept : application/json Content-Type : application/x-www-form-urlencoded
I receive response 200 OK: BODY: { "WSSE":"UsernameToken Username=\"a@a.it\", PasswordDigest=\"R82hdPWyV3PoTliW5O1aoSkKRZk=\", Nonce=\"YmUwZDVhNDliNzZiM2QzZA==\", Created=\"2013-05-23T07:57:03+02:00\"" }
After I call my api with a GET request:
localhost/project/web/app_dev.php/api/something
HEADERS:
Authorization : WSSE profile="UsernameToken" X-wsse : UsernameToken Username=\"a@a.it\", PasswordDigest=\"R82hdPWyV3PoTliW5O1aoSkKRZk=\", Nonce=\"YmUwZDVhNDliNzZiM2QzZA==\", Created=\"2013-05-23T07:57:03+02:00\" ACCEPT : application/json
I receive response 403 FORBIDDEN.
What is the error?
I have no experience with WSSE. I suggest you check your logs to see where the authentication failed
Someone can help me please?
how did this turn into a support thread?
What do you mean?
this issue tracker is meant to track issues/bugs with FosRestBundle, not help you learn how to use symfony. The mailing list is a much better place to ask for help with using the code.
I'm agree with you. I'm sorry! This is a problem linked with rest API and for this reason I ask help here.
Hi, I'm developing the RestApi for native mobile apps. I integrated WSSE authentication and FOSRestBundle. Now, if I log user (by browser) and after I use the api that's all right! But I need to use api without to log user. Ii is possible to do these steps?
-authentication by wsse -give user by wsse header in controller