【发布时间】:2019-12-15 04:48:23
【问题描述】:
如果用户必须注册,我的 Android Java 应用程序首先使用 AuthUI.getInstance() 创建一个 Firebase 帐户(带有密码的电子邮件)。创建帐户后,会出现一个对话框,通知用户他收到了一封验证电子邮件,他必须单击电子邮件中的验证链接。用户完成此操作后,他可以关闭对话框并继续在 Firestore 中配置他的帐户。
但对 Firestore 文档的所有请求都受安全规则保护,例如
allow read: if request.auth.uid != null && request.auth.token.email != null && request.auth.token.email_verified == true;
失败
com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: 权限缺失或不足
如果用户关闭应用程序,重新启动它并重新验证,那么它就可以工作(Firestore 请求没有权限问题)。
我做了几个测试。如果我将安全规则更改为
allow read: if request.auth.uid != null && request.auth.token.email != null;
一切正常,但从我的角度来看,它的安全性较低,因为无法保证电子邮件已通过验证。 Firestore 似乎仍然不知道该帐户已通过验证。
这是一个 Activity 示例:
package foo;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.firebase.ui.auth.AuthUI;
import com.google.android.gms.tasks.Task;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.firebase.Timestamp;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.Source;
import com.google.firebase.functions.FirebaseFunctions;
import com.google.firebase.functions.HttpsCallableReference;
import com.google.firebase.functions.HttpsCallableResult;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import fr.cinfo.planartis.R;
public class ExampleActivity extends AppCompatActivity {
private static final int SIGN_IN_REQUEST_CODE = 123;
private static final String VERIFICATION_EMAIL_SENT_TIMESTAMP_KEY = "verificationEmailSentTimestamp";
private FirebaseAuth firebaseAuth;
private FirebaseFirestore firestore;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
firebaseAuth = FirebaseAuth.getInstance();
firestore = FirebaseFirestore.getInstance();
if (firebaseAuth.getCurrentUser() == null) {
startUserSignInActivity();
} else {
performEmailVerification();
}
}
void startUserSignInActivity() {
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(Collections.singletonList(new AuthUI.IdpConfig.EmailBuilder().build()))
.setIsSmartLockEnabled(false, true)
.setTosAndPrivacyPolicyUrls("https://localhost/terms.html", "https://localhost/privacy.html")
.build(),
SIGN_IN_REQUEST_CODE);
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
if (requestCode == SIGN_IN_REQUEST_CODE) {
if (resultCode != RESULT_OK) { // ERRORS
// ... do something
finish();
return;
}
performEmailVerification();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
void performEmailVerification() {
if (firebaseAuth.getCurrentUser().isEmailVerified()) {
// Everything is OK
checkSomethingOnFirestore();
return;
}
final DocumentReference documentReference = firestore.document("users/" + firebaseAuth.getCurrentUser().getEmail());
documentReference.get(Source.DEFAULT).addOnCompleteListener((Task<DocumentSnapshot> task) -> {
if (task.isSuccessful()) {
final DocumentSnapshot userSnapshot = task.getResult();
if (userSnapshot.exists()) {
// Check if the first verification email was sent
final Object timestamp = userSnapshot.get(VERIFICATION_EMAIL_SENT_TIMESTAMP_KEY);
if (timestamp == null) {
firebaseAuth.getCurrentUser().sendEmailVerification().addOnCompleteListener((Task<Void> validationEmailTask) -> {
if (validationEmailTask.isSuccessful()) {
final Timestamp now = Timestamp.now();
documentReference.update(VERIFICATION_EMAIL_SENT_TIMESTAMP_KEY, now).addOnCompleteListener((Task<Void> updateUserAccountTask) -> {
if (!updateUserAccountTask.isSuccessful()) {
}
displayWarningAboutValidation();
});
}
else {
// email not sent, so display message
FirebaseAuth.getInstance().signOut();
displayInformationDialog(this, "Unable to send a verification email", null).show();
finish();
}
});
}
else {
displayWarningAboutValidation();
}
}
else {
Toast.makeText(this, "We are finalizing your account creation\nPlease wait a few seconds", Toast.LENGTH_LONG).show();
final HttpsCallableReference httpsCallableReference = FirebaseFunctions.getInstance().getHttpsCallable("finalizeUserAccount");
httpsCallableReference.setTimeout(15, TimeUnit.SECONDS);
httpsCallableReference.call(firebaseAuth.getCurrentUser().getEmail()).continueWith((Task<HttpsCallableResult> task12) -> {
if (!task12.isSuccessful()) {
displayInformationDialog(this, "The finalization of your account failed.", (DialogInterface dialog, int id) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
return null;
}
displayInformationDialog(this, "A new verification email was sent", (final DialogInterface dialog, final int id) -> {
// Reload current user
firebaseAuth.getCurrentUser().reload().addOnCompleteListener((Task<Void> task1) -> {
if (task1.isSuccessful()) {
performEmailVerification();
}
else {
FirebaseAuth.getInstance().signOut();
finish();
}
});
});
return null;
});
}
}
else {
displayInformationDialog(this, "Problem with the server", (DialogInterface dialog, int id) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
}
});
}
private void checkSomethingOnFirestore() {
firestore.collection("users").document(firebaseAuth.getCurrentUser().getEmail()).collection("documents").get().addOnCompleteListener(this, (Task<QuerySnapshot> task) -> {
if (!task.isSuccessful()) { // <======================================== PERMISSION_DENIED exception !!!!!!
displayInformationDialog(this, "Problem on Firestore", (final DialogInterface dialog, final int id) -> {
FirebaseAuth.getInstance().signOut();
finish();
})
.show();
return;
}
// Go on and do the job: for instance display the GUI or anything else
});
}
private void displayWarningAboutValidation() {
new AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("Read the verification email we sent and click on the link inside the email")
.setPositiveButton("I understand", (DialogInterface dialog, int id) -> firebaseAuth.getCurrentUser().reload().addOnCompleteListener((Task<Void> task) -> {
if (task.isSuccessful()) {
performEmailVerification();
}
else {
FirebaseAuth.getInstance().signOut();
finish();
}
}))
.setNeutralButton("Send back a verification email", (final DialogInterface dialog, final int which) -> firebaseAuth.getCurrentUser().sendEmailVerification().addOnCompleteListener((final Task<Void> task) -> {
dialog.dismiss();
if (task.isSuccessful()) {
// email sent
displayInformationDialog(this, "A new verification email was sent", (final DialogInterface dialog12, final int which12) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
firebaseAuth.getCurrentUser().reload().addOnCompleteListener((Task<Void> task1) -> {
if (task1.isSuccessful()) {
performEmailVerification();
}
else {
FirebaseAuth.getInstance().signOut();
finish();
}
});
}
else {
// email not sent, so display message
displayInformationDialog(this, "Unable to send a new verification email", (final DialogInterface dialog1, final int which1) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
}
}))
.show();
}
private AlertDialog displayInformationDialog(final Context context, final CharSequence message, final DialogInterface.OnClickListener positiveButtonOnclickListener) {
return new MaterialAlertDialogBuilder(context).setCancelable(false).setMessage(message).setPositiveButton("I understand", positiveButtonOnclickListener).setTitle("Planartis").setIcon(R.drawable.ic_logo_toolbar).show();
}
}
Firestore 的行为是否正确?为了避免重新启动应用程序和重新验证(用户友好性),我可以进行哪些更改?
【问题讨论】:
标签: java android google-cloud-firestore firebase-security