I am trying to use authentication to save profile but GAE Endpoint does not give 401 error even if user is null.
Instead Endpoint is responding with 200 ok and giving default results.
BTW this is part of the Udacity Course.
/**
* Creates or updates a Profile object associated with the given user
* object.
*
* #param user
* A User object injected by the cloud endpoints.
* #param profileForm
* A ProfileForm object sent from the client form.
* #return Profile object just created.
* #throws UnauthorizedException
* when the User object is null.
*/
// Declare this method as a method available externally through Endpoints
#ApiMethod(name = "saveProfile", path = "profile", httpMethod = HttpMethod.POST)
// The request that invokes this method should provide data that
// conforms to the fields defined in ProfileForm
// TODO 1 Pass the ProfileForm parameter
// TODO 2 Pass the User parameter
public Profile saveProfile(final User user) throws UnauthorizedException {
String userId = null;
String mainEmail = null;
String displayName = "Your name will go here";
TeeShirtSize teeShirtSize = TeeShirtSize.NOT_SPECIFIED;
// TODO 2
// If the user is not logged in, throw an UnauthorizedException
if(user== null){
throw new UnauthorizedException("Authorization Required!");
}
// TODO 1
// Set the teeShirtSize to the value sent by the ProfileForm, if sent
// otherwise leave it as the default value
// TODO 1
// Set the displayName to the value sent by the ProfileForm, if sent
// otherwise set it to null
// TODO 2
// Get the userId and mainEmail
// TODO 2
// If the displayName is null, set it to default value based on the user's email
// by calling extractDefaultDisplayNameFromEmail(...)
// Create a new Profile entity from the
// userId, displayName, mainEmail and teeShirtSize
Profile profile = new Profile(userId, displayName, mainEmail, teeShirtSize);
// TODO 3 (In Lesson 3)
// Save the Profile entity in the datastore
// Return the profile
return profile;
}
Unfortunately its a known issue. Go "Star" this issue to get updates on when it's fixed. There are a couple workarounds in the first link, heres a quote from it:
There are two workarounds (1) save the user and read back from the
store, if it refers to a valid account the user id will be populated
(this sucks because you pay the saving / loading / deletion cost for
each API access that is authenticated even if it is tiny, and
obviously some performance cost) and (2) you could use the google+ ID
but that is NOT the same as the user id.
Related
I want to generate the DocuSign embedded URL to get the documents signed by the community user.
I am able to achieve the requirement. Here is the solution
Initial things
DocuSign setup from both sides (Salesforce and DocuSign itself).
Signing user must be assigned “DocuSign Sender” permission set to sign the
document(s).
Create and send an envelope – First method
public static String sendEnvelope(String recordId) {
Id mySourceId = recordId; // The ID of the initiating Salesforce object
// Create an empty envelope and add a Salesforce Document and embedded signer
recipient
// The embedded signer will be the current user with sequence and routing
order 1 and role "Signer 1" by default
List<dfsle.Document> myDocuments = new List<dfsle.Document>();
// Content Version need to be param or queried
myDocuments =
dfsle.DocumentService.getDocuments(ContentVersion.getSObjectType(),
new Set<Id> { ContentVersionId(s) });
dfsle.Envelope dsEnvelope = dfsle.EnvelopeService.getEmptyEnvelope(
new dfsle.Entity(mySourceId))//The initiating Salesforce entity current SF user
.withDocuments(myDocuments)
.withRecipients(new List<dfsle.Recipient> {
dfsle.Recipient.newEmbeddedSigner() // An embedded signer
}
);
// Send the envelope.
dsEnvelope = dfsle.EnvelopeService.sendEnvelope(
dsEnvelope, // The envelope to send
true // Send now?
);
// Return string value of DocuSign envelope ID
return String.valueOf(dsEnvelope.docuSignId);
}
Host an embedded signing session – second method
// passing envId as parameter that we will receive from above method
public static String getEmbeddedSigningUrl(String envId) {
// url will be redirect URL
Url mySigningUrl = dfsle.SigningService.getEmbeddedSigningUrl(
dfsle.UUID.parse(envId), // envId value as a UUID
new URL(url) // url value as a URL
);
// Return string value of url to controller
return mySigningUrl.toExternalForm();
}
Variable Source id – It is the parent object record id where we want to store the signed documents back into salesforce.
Set of content version ids which are stored as files with s1,d1 tags etc.
myDocuments =
dfsle.DocumentService.getDocuments(ContentVersion.getSObjectType(),
new Set<Id> { ContentVersionId(s) });
Redirect URL - After signing the document(s) where we want to redirect the user.
In the case of using DocuSign in the context of Community user
Note: In the DocuSign Setup tab choose Configuration in the left menu, then go to the Settings tab and choose a user next to Enable System sender. This allows Community users to send envelopes even if they are not a member of the DocuSign account. In that case, envelopes will be sent from the admin user that you selected.
Important Points to remember
Embedded URL is valid only for 5 mins by default, if we want to increase the time
we will have to talk to the DocuSign support team and it can be increased max up
to 15 mins depending on the service plan.
References
https://developers.docusign.com/docs/salesforce/how-to/embedded-sending-signing/
Use Case - Salesforce Docusign implementation:
We have Parent and child object in salesforce where Parent have authorize signer and Relationship Manger email information and their each child object have document which we need to sign by authorize signer and relationship manger.
We need to send all the child object documents in single envelope. And when the signing ceremony completed we need to attach respective signed documents to their respective child records.
Currently, we can planning to do through Apex Toolkit or DocuSign rest API.
Example: Authorized signer and RM present on account record. And each contact associated with account having document which are attached by Contact person. Account owner should have button where it should fetch all the document from related contact, create envelope, should tagging the signature on each document and able to send to authorized Signer and RM.
Authorized Signer should received all the document with in single envelope. They signed all the document. Once Signed by them all the signed document should go back to respective contact.
Note: Business wants to see all the Recipients status and document sent to end user in salesforce as well.
Can you please provide input on this and share some sample as per our use case?
The flow here would be to pull the documents with DocumentService.getLinkedDocuments and then set up anchor tabs. If you want to send multiple documents, you'll need to set the Anchor Population Scope to Document. If docs are numbered in the order of the list 1, 2, 3. You will use .withOptions for the writeBack. Example can be found here:
https://www.docusign.com/blog/developers/whats-new-summer-21-apex-toolkit
And a sample code (except for the writeback part):
//Find your contact to add
Contact myContact = [SELECT Id, Name, Email FROM Contact WHERE Name = 'Snow Beard' LIMIT 1];
//This sets tab as an anchor tab. If using this with multiple documents,
// Ask customer support to set Account Setting Anchor Population Scope to Document.
dfsle.Tab hereTab = new dfsle.SignHereTab()
.withScale(1) // 1/2 scale
.withRequired(true) // Signing mandatory
.withDataLabel('SignHereMeHardy')
.withAnchor(
new dfsle.Tab.Anchor(
'Anchor1', // Anchor string
true, // allow white space in anchor string
true, // Anchor string is not case sensitive
'right', // Horizontal alignment in relation to the anchor text
true, // Ignore if the anchor text is not present in the document
true, // Must match the value of the anchor string in its entirety
'pixels', // Unit of the x and y offset properties
10, // X offset
10 // Y offset
)
)
//This places the tab on the first docunent in the list on page one. Requires DocuSign Support to set Anchor Population Scope to Document.
.withPosition(
new dfsle.Tab.Position(
1, //Document id matches order of documents
1, //Page id
null,
null,
20,
20
)
);
//use the Recipient.fromSource method to create the Recipient
dfsle.Recipient myRecipient = dfsle.Recipient.fromSource
(
myContact.Name, // Recipient name
myContact.Email, // Recipient email
null, //Optional phone number
'Signer 1', //Role Name. Specify the exact role name from template if using a template or use Default 'Signer 1'
new dfsle.Entity(myContact.Id) //source object for the Recipient
)
.withTabs(new List<dfsle.Tab> { // Associate the tabs with this recipient
hereTab
});
Opportunity myOpportunity = [SELECT Id FROM Opportunity WHERE Name = 'Sailcloth' LIMIT 1];
//This pulls all the documents from the Opportunity Object and adds them to documents list
List<dfsle.Document> documents = dfsle.DocumentService.getLinkedDocuments
(
ContentVersion.getSObjectType(),
new Set<Id>{myOpportunity.Id},
false
);
// Create an empty envelope.
// This shows how to pull documents from an object in Salesforce. In this case an Opportunity
dfsle.Envelope myEnvelope = dfsle.EnvelopeService.getEmptyEnvelope(new dfsle.Entity(myOpportunity.Id))
.withRecipients(new List<dfsle.Recipient> { myRecipient })
.withDocuments(documents);
// Send the envelope
try {
dfsle.EnvelopeService.sendEnvelope(myEnvelope, true);
} catch (dfsle.APIException ex) {
system.debug(ex);
if (ex.error.code == dfsle.APIErrorCode.CONSENT_REQUIRED) {
// user is a valid member of the DocuSign account, but has not granted consent to this application
} else {
// handle other errors
}
}
I am referencing #MinWan 's awesome answer in this post Google Cloud Endpoints and user's authentication, where he describes a way to add custom headers to a request against App Engine's Cloud Endpoints.
It becomes clear that we can add a custom header and write an authenticator per each service (e.g. Google, Twitter, Facebook) against which we want to authenicate, where each authenticator reads a specific header and authenticates against the service. If the token is valid, a service typically returns a response with an email address or user id, plus some extra information [A], from which we generate a com.google.api.server.spi.auth.common.User, which is later passed into the endpoint method as com.google.appengine.api.users.User.
First question: Why do we have two different User entities, e.g. users with different namespaces? As it seems, these are neither sub/superclasses, so they are possibly explicitly cast behind the scenes.
Second question: The problem that comes with the explicitly cast User entity and that there is no custom field where I could put the extra information [A] returned by the service, is that the extra information is lost. Such extra information may be helpful for matching the oauth2 user of the external service to a local user or to oauth2 users returned by other services.
Any input? What's the suggested way of handling multiple authentication services?
Just tested, and you can definitely subclass User to contain whichever private fields you want. Just use class inheritance polymorphism to return an object of that type from the Authenticator method, without changing the type from default User in the method signature.
import javax.servlet.http.HttpServletRequest;
import com.google.api.server.spi.auth.common.User;
import com.google.api.server.spi.config.Authenticator;
public class BazUser extends User {
private String secret; // extra piece of data held by this User
public BazUser(String email) {
super(email);
this.secret = "notasecret";
}
public BazUser (String email, String secret) {
super (email);
this.secret = secret;
}
}
public class BazAuthenticator implements Authenticator {
public User authenticate(HttpServletRequest req) {
return new BazUser ("userid#baz.com", "secret");
}
}
Functionally, everything works with:
import com.google.api.server.spi.auth.common.User;
even with gradle:
compile 'com.google.endpoints:endpoints-framework:2.0.0-beta.11'
The IDE warning can be cleared by including #SuppressWarnings("ResourceParameter") as follows:
/**
* Adds a new PmpUser.
*
* #param pmpUser pmpUser object
*/
#SuppressWarnings("ResourceParameter")
#ApiMethod(
name = "pmpUser.post",
path = "pmpUser",
httpMethod = ApiMethod.HttpMethod.POST)
...
I am trying to build a job site using App Engine using GCM. Where people can adverstise and others will be notified. When some one post a message, I want append their email id along with the message to make sure that message is appropriate and it doesn't have any harm, so it might be easier to find out that person incase if someone tries to play around with that. I am forcing the user to login to gmail account, before they do anything.
I was trying to use UserService to retrieve the email Id and name, but always giving me null. Not sure where I am doing mistake.
#ApiMethod(name = "sendMessage")
public void sendMessage(#Named("message") String message)
throws IOException {
Sender sender = new Sender(API_KEY);
// create a MessageData entity with a timestamp of when it was
// received, and persist it
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
MessageData messageObj = new MessageData();
if(user != null)
messageObj.setMessage(message + " User : " + user.getUserId() + "Email " + user.getEmail());
else
messageObj.setMessage(message);
Any help is greatly appreciated!
UserService.getCurrentUser() will return the a User object if the user is logged in, and null otherwise.
You can call UserService.isUserLoggedIn() to test if the user is logged in.
https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/users/UserService#getCurrentUser()
I have a model 'events' that holds its own data + user id. How do I grant access only to the user to whom the event belongs?
You can just add a check at the beginning of your view method to see if the Authenticated user id matches that of the event. If not, redirect them back with an "access denied" flash message. For example, in your EventsController add:
public function view($event_id) {
// Make sure the event exists at all
if (!$this->Event->exists($event_id)) {
throw new NotFoundException(__('No such event.'));
}
// Get the event data
$event = $this->Event->findById($event_id);
// Match the authenticated user with the user_id of the event.
if ($this->Auth->User('id') != $event['Event']['user_id']) {
// No match, redirect the user back to the index action.
$this->Session->setFlash(__('This is not your event!'));
$this->redirect(array('action' => 'index'));
}
/**
* If this point is reached, the user is the owner of the event.
* The rest of your logic goes below this point.
*/
}