Fix issue #645 - Verify user emails

This commit is contained in:
Robin Shen 2022-03-19 09:04:01 +08:00 committed by Robin Shen
parent 2c0148003b
commit 0f8bc2b958
103 changed files with 2555 additions and 1099 deletions

View File

@ -362,7 +362,7 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.67</version>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.unbescape</groupId>

View File

@ -100,6 +100,7 @@ import io.onedev.server.entitymanager.CodeCommentManager;
import io.onedev.server.entitymanager.CodeCommentQueryPersonalizationManager;
import io.onedev.server.entitymanager.CodeCommentReplyManager;
import io.onedev.server.entitymanager.CommitQueryPersonalizationManager;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.GitLfsLockManager;
import io.onedev.server.entitymanager.GroupAuthorizationManager;
import io.onedev.server.entitymanager.GroupManager;
@ -143,6 +144,7 @@ import io.onedev.server.entitymanager.impl.DefaultCodeCommentManager;
import io.onedev.server.entitymanager.impl.DefaultCodeCommentQueryPersonalizationManager;
import io.onedev.server.entitymanager.impl.DefaultCodeCommentReplyManager;
import io.onedev.server.entitymanager.impl.DefaultCommitQueryPersonalizationManager;
import io.onedev.server.entitymanager.impl.DefaultEmailAddressManager;
import io.onedev.server.entitymanager.impl.DefaultGitLfsLockManager;
import io.onedev.server.entitymanager.impl.DefaultGroupAuthorizationManager;
import io.onedev.server.entitymanager.impl.DefaultGroupManager;
@ -432,6 +434,7 @@ public class CoreModule extends AbstractPluginModule {
bind(LinkSpecManager.class).to(DefaultLinkSpecManager.class);
bind(IssueLinkManager.class).to(DefaultIssueLinkManager.class);
bind(LinkAuthorizationManager.class).to(DefaultLinkAuthorizationManager.class);
bind(EmailAddressManager.class).to(DefaultEmailAddressManager.class);
bind(WebHookManager.class);

View File

@ -25,6 +25,7 @@ import io.onedev.server.buildspec.job.action.notificationreceiver.NotificationRe
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Group;
import io.onedev.server.model.User;
@ -37,7 +38,7 @@ public class NotificationReceiver {
}
public static NotificationReceiver parse(String receiverString, @Nullable Build build) {
Collection<String> emails = new HashSet<>();
Collection<String> emailAddresses = new HashSet<>();
CharStream is = CharStreams.fromString(receiverString);
NotificationReceiverLexer lexer = new NotificationReceiverLexer(is);
@ -61,22 +62,27 @@ public class NotificationReceiver {
String userName = getValue(criteria.userCriteria().Value());
User user = OneDev.getInstance(UserManager.class).findByName(userName);
if (user != null)
emails.add(user.getEmail());
addEmailAddress(emailAddresses, user);
else
throw new ExplicitException("Unable to find user '" + userName + "'");
} else if (criteria.groupCriteria() != null) {
String groupName = getValue(criteria.groupCriteria().Value());
Group group = OneDev.getInstance(GroupManager.class).find(groupName);
if (group != null)
emails.addAll(group.getMembers().stream().map(it->it.getEmail()).collect(Collectors.toList()));
else
if (group != null) {
emailAddresses.addAll(group.getMembers().stream()
.map(it->it.getPrimaryEmailAddress())
.filter(it-> it!=null && it.isVerified())
.map(it->it.getValue())
.collect(Collectors.toList()));
} else {
throw new ExplicitException("Unable to find group '" + groupName + "'");
}
} else if (criteria.Committers() != null) {
if (build != null) {
for (RevCommit commit: build.getCommits(null)) {
PersonIdent committer = commit.getCommitterIdent();
if (committer != null && committer.getEmailAddress() != null)
emails.add(committer.getEmailAddress());
emailAddresses.add(committer.getEmailAddress());
}
}
} else if (criteria.Authors() != null) {
@ -84,7 +90,7 @@ public class NotificationReceiver {
for (RevCommit commit: build.getCommits(null)) {
PersonIdent author = commit.getAuthorIdent();
if (author != null && author.getEmailAddress() != null)
emails.add(author.getEmailAddress());
emailAddresses.add(author.getEmailAddress());
}
}
} else if (criteria.CommittersSincePreviousSuccessful() != null) {
@ -92,7 +98,7 @@ public class NotificationReceiver {
for (RevCommit commit: build.getCommits(Build.Status.SUCCESSFUL)) {
PersonIdent committer = commit.getCommitterIdent();
if (committer != null && committer.getEmailAddress() != null)
emails.add(committer.getEmailAddress());
emailAddresses.add(committer.getEmailAddress());
}
}
} else if (criteria.AuthorsSincePreviousSuccessful() != null) {
@ -100,18 +106,24 @@ public class NotificationReceiver {
for (RevCommit commit: build.getCommits(Build.Status.SUCCESSFUL)) {
PersonIdent author = commit.getAuthorIdent();
if (author != null && author.getEmailAddress() != null)
emails.add(author.getEmailAddress());
emailAddresses.add(author.getEmailAddress());
}
}
} else if (criteria.Submitter() != null) {
if (build != null && build.getSubmitter() != null)
emails.add(build.getSubmitter().getEmail());
addEmailAddress(emailAddresses, build.getSubmitter());
} else {
throw new RuntimeException("Unexpected notification receiver criteria");
}
}
return new NotificationReceiver(emails);
return new NotificationReceiver(emailAddresses);
}
private static void addEmailAddress(Collection<String> emailAddressValues, User user) {
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
emailAddressValues.add(emailAddress.getValue());
}
private static String getValue(TerminalNode terminal) {

View File

@ -0,0 +1,24 @@
package io.onedev.server.entitymanager;
import javax.annotation.Nullable;
import org.eclipse.jgit.lib.PersonIdent;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.persistence.dao.EntityManager;
public interface EmailAddressManager extends EntityManager<EmailAddress> {
@Nullable
EmailAddress findByValue(String value);
@Nullable
EmailAddress findByPersonIdent(PersonIdent personIdent);
void setAsPrimary(EmailAddress emailAddress);
void useForGitOperations(EmailAddress emailAddress);
void sendVerificationEmail(EmailAddress emailAddress);
}

View File

@ -5,12 +5,8 @@ import java.util.List;
import javax.annotation.Nullable;
import org.eclipse.jgit.lib.PersonIdent;
import io.onedev.server.model.User;
import io.onedev.server.model.support.SsoInfo;
import io.onedev.server.persistence.dao.EntityManager;
import io.onedev.server.util.facade.UserFacade;
public interface UserManager extends EntityManager<User> {
@ -69,42 +65,15 @@ public interface UserManager extends EntityManager<User> {
@Nullable
User findByFullName(String fullName);
@Nullable
User findBySsoInfo(SsoInfo ssoInfo);
@Nullable
User findByAccessToken(String accessToken);
/**
* Find user of specified name.
* <p>
* @param userName
* name of the user
* @return
* matching user, or <tt>null</tt> if not found
*/
@Nullable
User findByEmail(String email);
@Nullable
UserFacade findFacadeByEmail(String email);
User findByVerifiedEmailAddress(String emailAddress);
/**
* Find user by person
* <p>
* @param person
* Git person representation
* @return
* matching user, or <tt>null</tt> if not found
*/
@Nullable
User find(PersonIdent person);
List<User> query(@Nullable String term, int firstResult, int maxResults);
@Nullable
UserFacade findFacade(PersonIdent person);
@Nullable
UserFacade getFacade(Long userId);
int count(String term);
List<User> queryAndSort(Collection<User> topUsers);

View File

@ -0,0 +1,223 @@
package io.onedev.server.entitymanager.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.lib.PersonIdent;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import io.onedev.commons.loader.Listen;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.notification.MailManager;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.BaseEntityManager;
import io.onedev.server.persistence.dao.Dao;
@Singleton
public class DefaultEmailAddressManager extends BaseEntityManager<EmailAddress> implements EmailAddressManager {
private final SettingManager settingManager;
private final MailManager mailManager;
private final TransactionManager transactionManager;
private final SessionManager sessionManager;
private final Map<String, Long> idCache = new ConcurrentHashMap<>();
@Inject
public DefaultEmailAddressManager(Dao dao, SettingManager settingManager, MailManager mailManager,
TransactionManager transactionManager, SessionManager sessionManager) {
super(dao);
this.settingManager = settingManager;
this.mailManager = mailManager;
this.transactionManager = transactionManager;
this.sessionManager = sessionManager;
}
@Listen
@Sessional
public void on(SystemStarted event) {
for (EmailAddress address: query())
idCache.put(address.getValue(), address.getId());
}
@Sessional
@Override
public EmailAddress findByValue(String value) {
Long id = idCache.get(value.toLowerCase());
return id != null? load(id): null;
}
@Sessional
@Override
public EmailAddress findByPersonIdent(PersonIdent personIdent) {
if (StringUtils.isNotBlank(personIdent.getEmailAddress()))
return findByValue(personIdent.getEmailAddress());
else
return null;
}
@Transactional
@Override
public void setAsPrimary(EmailAddress emailAddress) {
for (EmailAddress each: emailAddress.getOwner().getEmailAddresses())
each.setPrimary(false);
emailAddress.setPrimary(true);
}
@Transactional
@Override
public void useForGitOperations(EmailAddress emailAddress) {
for (EmailAddress each: emailAddress.getOwner().getEmailAddresses())
each.setGit(false);
emailAddress.setGit(true);
}
@Transactional
@Override
public void delete(EmailAddress emailAddress) {
super.delete(emailAddress);
User user = emailAddress.getOwner();
user.getEmailAddresses().remove(emailAddress);
if (!user.getSortedEmailAddresses().isEmpty()) {
if (user.getPrimaryEmailAddress() == null)
user.getSortedEmailAddresses().iterator().next().setPrimary(true);
if (user.getGitEmailAddress() == null)
user.getSortedEmailAddresses().iterator().next().setGit(true);
}
}
@Transactional
@Override
public void save(EmailAddress emailAddress) {
boolean isNew = emailAddress.isNew();
emailAddress.setValue(emailAddress.getValue().toLowerCase());
User user = emailAddress.getOwner();
if (user.getEmailAddresses().isEmpty()) {
emailAddress.setPrimary(true);
emailAddress.setGit(true);
}
super.save(emailAddress);
user.getEmailAddresses().add(emailAddress);
if (isNew && settingManager.getMailSetting() != null && !emailAddress.isVerified()) {
Long addressId = emailAddress.getId();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
sessionManager.runAsync(new Runnable() {
@Override
public void run() {
sendVerificationEmail(load(addressId));
}
});
}
});
}
}
@Transactional
@Listen
public void on(EntityRemoved event) {
if (event.getEntity() instanceof EmailAddress) {
String value = ((EmailAddress)event.getEntity()).getValue();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
idCache.remove(value);
}
});
} else if (event.getEntity() instanceof User) {
User user = (User) event.getEntity();
Collection<String> values = user.getEmailAddresses().stream()
.map(it->it.getValue()).collect(Collectors.toList());
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
idCache.keySet().removeAll(values);
}
});
}
}
@Transactional
@Listen
public void on(EntityPersisted event) {
if (event.getEntity() instanceof EmailAddress) {
EmailAddress emailAddress = (EmailAddress) event.getEntity();
String value = emailAddress.getValue();
Long id = emailAddress.getId();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
idCache.put(value, id);
}
});
}
}
@Override
public void sendVerificationEmail(EmailAddress emailAddress) {
Preconditions.checkState(settingManager.getMailSetting() != null
&& !emailAddress.isVerified());
User user = emailAddress.getOwner();
String serverUrl = settingManager.getSystemSetting().getServerUrl();
String verificationUrl = String.format("%s/verify-email-address/%d/%s",
serverUrl, emailAddress.getId(), emailAddress.getVerficationCode());
String htmlBody = String.format("Hello,"
+ "<p style='margin: 16px 0;'>"
+ "The account \"%s\" at \"%s\" tries to use email address '%s', please visit below link to verify if this is you:<br><br>"
+ "<a href='%s'>%s</a>",
user.getName(), serverUrl, emailAddress.getValue(), verificationUrl, verificationUrl);
String textBody = String.format("Hello,\n\n"
+ "The account \"%s\" at \"%s\" tries to use email address \"%s\", please visit below link to verify if this is you:\n\n"
+ "%s",
user.getName(), serverUrl, emailAddress.getValue(), verificationUrl);
mailManager.sendMail(
settingManager.getMailSetting(),
Arrays.asList(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(),
"[Verification] Please Verify Your Email Address",
htmlBody, textBody, null, null);
}
}

View File

@ -296,6 +296,7 @@ public class DefaultPullRequestManager extends BaseEntityManager<PullRequest> im
ObjectId mergeCommitId = ObjectId.fromString(
Preconditions.checkNotNull(mergePreview.getMergeCommitHash()));
PersonIdent user = SecurityUtils.getUser().asPerson();
Project project = request.getTargetProject();
MergeStrategy mergeStrategy = mergePreview.getMergeStrategy();

View File

@ -1,36 +1,40 @@
package io.onedev.server.entitymanager.impl;
import static io.onedev.server.model.User.PROP_PASSWORD;
import static io.onedev.server.model.User.PROP_SSO_INFO;
import static io.onedev.server.model.support.SsoInfo.PROP_CONNECTOR;
import static io.onedev.server.model.support.SsoInfo.PROP_SUBJECT;
import static io.onedev.server.model.User.PROP_SSO_CONNECTOR;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.lib.PersonIdent;
import org.hibernate.ReplicationMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.query.Query;
import io.onedev.commons.loader.Listen;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.IssueFieldManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.BranchProtection;
import io.onedev.server.model.support.SsoInfo;
import io.onedev.server.model.support.TagProtection;
import io.onedev.server.persistence.IdManager;
import io.onedev.server.persistence.TransactionManager;
@ -39,7 +43,6 @@ import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.BaseEntityManager;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.util.usage.Usage;
@Singleton
@ -53,48 +56,26 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
private final IdManager idManager;
private final EmailAddressManager emailAddressManager;
private final TransactionManager transactionManager;
private final Map<String, Long> userIdByEmail = new ConcurrentHashMap<>();
private final Map<Long, UserFacade> cache = new ConcurrentHashMap<>();
private final Map<String, Long> idCache = new ConcurrentHashMap<>();
@Inject
public DefaultUserManager(Dao dao, ProjectManager projectManager, SettingManager settingManager,
IssueFieldManager issueFieldManager, IdManager idManager, TransactionManager transactionManager) {
IssueFieldManager issueFieldManager, IdManager idManager,
EmailAddressManager emailAddressManager, TransactionManager transactionManager) {
super(dao);
this.projectManager = projectManager;
this.settingManager = settingManager;
this.issueFieldManager = issueFieldManager;
this.idManager = idManager;
this.emailAddressManager = emailAddressManager;
this.transactionManager = transactionManager;
}
@SuppressWarnings("unchecked")
@Sessional
@Listen
public void on(SystemStarted event) {
String queryString = String.format("select id, %s, %s, %s, %s, %s from User",
User.PROP_NAME, User.PROP_FULL_NAME, User.PROP_EMAIL, User.PROP_GIT_EMAIL, User.PROP_ALTERNATE_EMAILS);
Query<?> query = dao.getSession().createQuery(queryString);
for (Object[] fields: (List<Object[]>)query.list()) {
Long userId = (Long) fields[0];
String name = (String) fields[1];
String fullName = (String) fields[2];
String email = (String) fields[3];
String gitEmail = (String) fields[4];
List<String> alternateEmails = (List<String>) fields[5];
userIdByEmail.put(email, userId);
if (gitEmail != null)
userIdByEmail.put(gitEmail, userId);
for (String alternateEmail: alternateEmails)
userIdByEmail.put(alternateEmail, userId);
cache.put(userId, new UserFacade(userId, name, fullName, email, gitEmail, alternateEmails));
}
}
@Transactional
@Override
public void replicate(User user) {
@ -105,6 +86,8 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
@Transactional
@Override
public void save(User user, String oldName) {
user.setName(user.getName().toLowerCase());
dao.persist(user);
if (oldName != null && !oldName.equals(user.getName())) {
@ -120,20 +103,6 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
issueFieldManager.onRenameUser(oldName, user.getName());
}
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
userIdByEmail.put(user.getEmail(), user.getId());
if (user.getGitEmail() != null)
userIdByEmail.put(user.getGitEmail(), user.getId());
for (String alternateEmail: user.getAlternateEmails())
userIdByEmail.put(alternateEmail, user.getId());
cache.put(user.getId(), new UserFacade(user));
}
});
}
@Override
@ -249,29 +218,19 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
query.executeUpdate();
dao.remove(user);
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
cache.remove(user.getId());
userIdByEmail.remove(user.getEmail());
if (user.getGitEmail() != null)
userIdByEmail.remove(user.getGitEmail());
for (String email: user.getAlternateEmails())
userIdByEmail.remove(email);
}
});
}
@Sessional
@Override
public User findByName(String userName) {
EntityCriteria<User> criteria = newCriteria();
criteria.add(Restrictions.ilike(User.PROP_NAME, userName));
criteria.setCacheable(true);
return find(criteria);
userName = userName.toLowerCase();
Long id = idCache.get(userName);
if (id != null) {
User user = get(id);
if (user != null && user.getName().equals(userName))
return user;
}
return null;
}
@Sessional
@ -292,16 +251,6 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
return find(criteria);
}
@Sessional
@Override
public User findBySsoInfo(SsoInfo ssoInfo) {
EntityCriteria<User> criteria = newCriteria();
criteria.add(Restrictions.eq(User.PROP_SSO_INFO + "." + SsoInfo.PROP_CONNECTOR, ssoInfo.getConnector()));
criteria.add(Restrictions.eq(User.PROP_SSO_INFO + "." + SsoInfo.PROP_SUBJECT, ssoInfo.getSubject()));
criteria.setCacheable(true);
return find(criteria);
}
@Override
public List<User> query() {
EntityCriteria<User> criteria = newCriteria();
@ -315,47 +264,6 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
return count(true);
}
@Sessional
@Override
public UserFacade findFacadeByEmail(String email) {
Long userId = userIdByEmail.get(email);
if (userId != null) {
UserFacade user = cache.get(userId);
if (user != null && user.isUsingEmail(email))
return user;
}
return null;
}
@Sessional
@Override
public UserFacade findFacade(PersonIdent person) {
if (StringUtils.isNotBlank(person.getEmailAddress()))
return findFacadeByEmail(person.getEmailAddress());
else
return null;
}
@Sessional
@Override
public UserFacade getFacade(Long userId) {
return cache.get(userId);
}
@Sessional
@Override
public User findByEmail(String email) {
UserFacade facade = findFacadeByEmail(email);
return facade!=null? load(facade.getId()): null;
}
@Sessional
@Override
public User find(PersonIdent person) {
UserFacade facade = findFacade(person);
return facade!=null? load(facade.getId()): null;
}
@Override
public List<User> queryAndSort(Collection<User> topUsers) {
List<User> users = query();
@ -365,12 +273,53 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
return users;
}
@Sessional
@Listen
public void on(SystemStarted event) {
for (User user: query())
idCache.put(user.getName(), user.getId());
}
@Transactional
@Listen
public void on(EntityRemoved event) {
if (event.getEntity() instanceof User) {
User user = (User) event.getEntity();
String name = user.getName();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
idCache.remove(name);
}
});
}
}
@Transactional
@Listen
public void on(EntityPersisted event) {
if (event.getEntity() instanceof User) {
User user = (User) event.getEntity();
String name = user.getName();
Long id = user.getId();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
idCache.put(name, id);
}
});
}
}
@Transactional
@Override
public void onRenameSsoConnector(String oldName, String newName) {
String connectorProp = PROP_SSO_INFO + "." + PROP_CONNECTOR;
Query<?> query = getSession().createQuery(String.format("update User set %s=:newName "
+ "where %s=:oldName", connectorProp, connectorProp));
+ "where %s=:oldName", oldName, newName));
query.setParameter("oldName", oldName);
query.setParameter("newName", newName);
query.executeUpdate();
@ -379,13 +328,70 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
@Transactional
@Override
public void onDeleteSsoConnector(String name) {
String connectorProp = PROP_SSO_INFO + "." + PROP_CONNECTOR;
String subjectProp = PROP_SSO_INFO + "." + PROP_SUBJECT;
Query<?> query = getSession().createQuery(String.format("update User set %s=null, %s='%s', %s='12345' "
Query<?> query = getSession().createQuery(String.format("update User set %s=null, %s='12345' "
+ "where %s=:name",
connectorProp, subjectProp, UUID.randomUUID().toString(), PROP_PASSWORD, connectorProp));
PROP_SSO_CONNECTOR, PROP_PASSWORD, PROP_SSO_CONNECTOR));
query.setParameter("name", name);
query.executeUpdate();
}
private Predicate[] getPredicates(CriteriaBuilder builder, CriteriaQuery<?> query,
Root<User> root, String term) {
if (term != null) {
term = "%" + term.toLowerCase() + "%";
Subquery<EmailAddress> addressQuery = query.subquery(EmailAddress.class);
Root<EmailAddress> addressRoot = addressQuery.from(EmailAddress.class);
addressQuery.select(addressRoot);
Predicate ownerPredicate = builder.equal(addressRoot.get(EmailAddress.PROP_OWNER), root);
Predicate valuePredicate = builder.like(addressRoot.get(EmailAddress.PROP_VALUE), term);
return new Predicate[] {
builder.gt(root.get(AbstractEntity.PROP_ID), 0),
builder.or(
builder.like(root.get(User.PROP_NAME), term),
builder.like(builder.lower(root.get(User.PROP_FULL_NAME)), term),
builder.exists(addressQuery.where(ownerPredicate, valuePredicate)))};
} else {
return new Predicate[] {builder.gt(root.get(AbstractEntity.PROP_ID), 0)};
}
}
@Override
public List<User> query(String term, int firstResult, int maxResults) {
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = builder.createQuery(User.class);
Root<User> root = criteriaQuery.from(User.class);
criteriaQuery.where(getPredicates(builder, criteriaQuery, root, term));
Query<User> query = getSession().createQuery(criteriaQuery);
query.setFirstResult(firstResult);
query.setMaxResults(maxResults);
return query.getResultList();
}
@Override
public int count(String term) {
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
Root<User> root = criteriaQuery.from(User.class);
criteriaQuery.select(builder.count(root));
criteriaQuery.where(getPredicates(builder, criteriaQuery, root, term));
return getSession().createQuery(criteriaQuery).uniqueResult().intValue();
}
@Sessional
@Override
public User findByVerifiedEmailAddress(String emailAddressValue) {
EmailAddress emailAddress = emailAddressManager.findByValue(emailAddressValue);
if (emailAddress != null && emailAddress.isVerified())
return emailAddress.getOwner();
else
return null;
}
}

View File

@ -4,7 +4,7 @@ import java.util.Collection;
import java.util.stream.Collectors;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.model.PullRequestUpdate;
import io.onedev.server.model.User;
@ -32,8 +32,9 @@ public class PullRequestUpdated extends PullRequestEvent {
if (committers == null) {
committers = getUpdate().getCommits()
.stream()
.map(it->OneDev.getInstance(UserManager.class).find(it.getCommitterIdent()))
.filter(it->it!=null)
.map(it->OneDev.getInstance(EmailAddressManager.class).findByPersonIdent(it.getCommitterIdent()))
.filter(it -> it!=null && it.isVerified())
.map(it->it.getOwner())
.collect(Collectors.toSet());
}
return committers;

View File

@ -40,14 +40,13 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import io.onedev.commons.loader.Listen;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.PathUtils;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.RefUpdated;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.system.SystemStarted;
@ -60,6 +59,7 @@ import io.onedev.server.git.command.ListNumStatsCommand;
import io.onedev.server.git.command.LogCommand;
import io.onedev.server.git.command.RevListCommand;
import io.onedev.server.git.command.RevListCommand.Order;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
@ -73,7 +73,6 @@ import io.onedev.server.util.Pair;
import io.onedev.server.util.concurrent.BatchWorkManager;
import io.onedev.server.util.concurrent.BatchWorker;
import io.onedev.server.util.concurrent.Prioritized;
import io.onedev.server.util.facade.UserFacade;
import jetbrains.exodus.ArrayByteIterable;
import jetbrains.exodus.ByteIterable;
import jetbrains.exodus.backup.BackupStrategy;
@ -286,7 +285,7 @@ public class DefaultCommitInfoManager extends AbstractMultiEnvironmentManager im
private final SessionManager sessionManager;
private final UserManager userManager;
private final EmailAddressManager emailAddressManager;
private final Map<Long, List<String>> filesCache = new ConcurrentHashMap<>();
@ -296,12 +295,13 @@ public class DefaultCommitInfoManager extends AbstractMultiEnvironmentManager im
@Inject
public DefaultCommitInfoManager(ProjectManager projectManager, StorageManager storageManager,
BatchWorkManager batchWorkManager, SessionManager sessionManager, UserManager userManager) {
BatchWorkManager batchWorkManager, SessionManager sessionManager,
EmailAddressManager emailAddressManager) {
this.projectManager = projectManager;
this.storageManager = storageManager;
this.batchWorkManager = batchWorkManager;
this.sessionManager = sessionManager;
this.userManager = userManager;
this.emailAddressManager = emailAddressManager;
}
private boolean isCommitCollected(byte[] commitBytes) {
@ -1016,26 +1016,23 @@ public class DefaultCommitInfoManager extends AbstractMultiEnvironmentManager im
Store emailToIndexStore = getStore(env, USER_TO_INDEX_STORE);
Store pathToIndexStore = getStore(env, PATH_TO_INDEX_STORE);
Store commitCountStore = getStore(env, COMMIT_COUNTS_STORE);
return env.computeInReadonlyTransaction(new TransactionalComputable<Integer>() {
@Override
public Integer compute(Transaction txn) {
Collection<String> emails = Sets.newHashSet(user.getEmail());
if (user.getGitEmail() != null)
emails.add(user.getGitEmail());
emails.addAll(user.getAlternateEmails());
int count = 0;
for (String email: emails) {
int userIndex = readInt(emailToIndexStore, txn, new StringByteIterable(email), -1);
AtomicInteger count = new AtomicInteger(0);
user.getEmailAddresses().stream().filter(it->it.isVerified()).forEach(it-> {
int userIndex = readInt(emailToIndexStore, txn, new StringByteIterable(it.getValue()), -1);
if (userIndex != -1) {
int pathIndex = readInt(pathToIndexStore, txn, new StringByteIterable(path), -1);
if (pathIndex != -1) {
long commitCountKey = (userIndex<<32)|pathIndex;
count += readInt(commitCountStore, txn, new LongByteIterable(commitCountKey), 0);
count.addAndGet(readInt(commitCountStore, txn, new LongByteIterable(commitCountKey), 0));
}
}
}
return count;
});
return count.get();
}
});
}
@ -1341,9 +1338,15 @@ public class DefaultCommitInfoManager extends AbstractMultiEnvironmentManager im
byte[] userBytes = readBytes(indexToUserStore, txn, new IntByteIterable(userIndex));
if (userBytes != null) {
NameAndEmail user = (NameAndEmail) SerializationUtils.deserialize(userBytes);
UserFacade facade = userManager.findFacadeByEmail(user.getEmailAddress());
if (facade != null)
user = facade.getNameAndEmail();
EmailAddress emailAddress = emailAddressManager.findByValue(user.getEmailAddress());
if (emailAddress != null && emailAddress.isVerified()) {
User owner = emailAddress.getOwner();
EmailAddress primaryEmailAddress = owner.getPrimaryEmailAddress();
if (primaryEmailAddress != null && primaryEmailAddress.isVerified())
user = new NameAndEmail(owner.getDisplayName(), primaryEmailAddress.getValue());
else
user = new NameAndEmail(owner.getDisplayName(), emailAddress.getValue());
}
userOpt = Optional.of(user);
} else {
userOpt = Optional.empty();

View File

@ -8,7 +8,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -30,11 +29,13 @@ import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.commons.utils.ExceptionUtils;
import io.onedev.commons.utils.FileUtils;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.LinkSpecManager;
import io.onedev.server.entitymanager.RoleManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.system.SystemStarting;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.LinkSpec;
import io.onedev.server.model.Role;
import io.onedev.server.model.Setting;
@ -63,6 +64,7 @@ import io.onedev.server.util.init.ManualConfig;
import io.onedev.server.util.init.Skippable;
import io.onedev.server.util.schedule.SchedulableTask;
import io.onedev.server.util.schedule.TaskScheduler;
import io.onedev.server.web.util.NewUserBean;
@Singleton
public class DefaultDataManager implements DataManager, Serializable {
@ -85,6 +87,8 @@ public class DefaultDataManager implements DataManager, Serializable {
private final LinkSpecManager linkSpecManager;
private final EmailAddressManager emailAddressManager;
private String backupTaskId;
@Inject
@ -92,7 +96,7 @@ public class DefaultDataManager implements DataManager, Serializable {
SettingManager settingManager, PersistManager persistManager,
MailManager mailManager, Validator validator, TaskScheduler taskScheduler,
PasswordService passwordService, RoleManager roleManager,
LinkSpecManager linkSpecManager) {
LinkSpecManager linkSpecManager, EmailAddressManager emailAddressManager) {
this.userManager = userManager;
this.settingManager = settingManager;
this.validator = validator;
@ -102,6 +106,7 @@ public class DefaultDataManager implements DataManager, Serializable {
this.passwordService = passwordService;
this.roleManager = roleManager;
this.linkSpecManager = linkSpecManager;
this.emailAddressManager = emailAddressManager;
}
@SuppressWarnings({"serial"})
@ -113,8 +118,8 @@ public class DefaultDataManager implements DataManager, Serializable {
if (system == null) {
system = new User();
system.setId(User.SYSTEM_ID);
system.setName(User.SYSTEM_NAME);
system.setEmail("system email");
system.setName(User.SYSTEM_NAME.toLowerCase());
system.setFullName(User.SYSTEM_NAME);
system.setPassword("no password");
userManager.replicate(system);
}
@ -122,17 +127,13 @@ public class DefaultDataManager implements DataManager, Serializable {
if (unknown == null) {
unknown = new User();
unknown.setId(User.UNKNOWN_ID);
unknown.setName(User.UNKNOWN_NAME);
unknown.setEmail("unknown email");
unknown.setName(User.UNKNOWN_NAME.toLowerCase());
unknown.setFullName(User.UNKNOWN_NAME);
unknown.setPassword("no password");
userManager.replicate(unknown);
}
User administrator = userManager.get(User.ROOT_ID);
if (administrator == null) {
administrator = new User();
administrator.setId(User.ROOT_ID);
Set<String> excludedProperties = Sets.newHashSet(User.PROP_GIT_EMAIL, User.PROP_ALTERNATE_EMAILS);
manualConfigs.add(new ManualConfig("Create Administrator Account", null, administrator, excludedProperties) {
if (userManager.get(User.ROOT_ID) == null) {
manualConfigs.add(new ManualConfig("Create Administrator Account", null, new NewUserBean()) {
@Override
public Skippable getSkippable() {
@ -141,9 +142,20 @@ public class DefaultDataManager implements DataManager, Serializable {
@Override
public void complete() {
User user = (User) getSetting();
user.setPassword(passwordService.encryptPassword(user.getPassword()));
NewUserBean newUserBean = (NewUserBean) getSetting();
User user = new User();
user.setId(User.ROOT_ID);
user.setName(newUserBean.getName());
user.setFullName(newUserBean.getFullName());
user.setPassword(passwordService.encryptPassword(newUserBean.getPassword()));
userManager.replicate(user);
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue(newUserBean.getEmailAddress());
emailAddress.setVerificationCode(null);
emailAddress.setOwner(user);
emailAddressManager.save(emailAddress);
}
});
@ -400,9 +412,13 @@ public class DefaultDataManager implements DataManager, Serializable {
+ "Error detail:\n"
+ "%s",
url, Throwables.getStackTraceAsString(e));
mailManager.sendMail(Lists.newArrayList(root.getEmail()), Lists.newArrayList(),
Lists.newArrayList(), "[Backup] OneDev Database Auto-backup Failed",
htmlBody, textBody, null, null);
EmailAddress emailAddress = root.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMail(Lists.newArrayList(emailAddress.getValue()), Lists.newArrayList(),
Lists.newArrayList(), "[Backup] OneDev Database Auto-backup Failed",
htmlBody, textBody, null, null);
}
}
public Object writeReplace() throws ObjectStreamException {

View File

@ -63,8 +63,9 @@ public class ResetAdminPassword extends DefaultPersistManager {
System.exit(1);
}
String password = Bootstrap.command.getArgs()[0];
root.setPassword(passwordService.encryptPassword(password));
root.setSsoConnector(null);
root.setTwoFactorAuthentication(null);
root.setPassword(passwordService.encryptPassword(password));
userManager.save(root);
// wait for a short period to have embedded db flushing data

View File

@ -3796,10 +3796,62 @@ public class DataMigrator {
}
}
// Migrate to 6.4.0
// Migrate to 7.0.0
private void migrate82(File dataDir, Stack<Integer> versions) {
Set<String> userNames = new HashSet<>();
Map<String, String> primaryEmails = new HashMap<>();
Map<String, String> gitEmails = new HashMap<>();
Map<String, String> alternateEmails = new HashMap<>();
for (File file: dataDir.listFiles()) {
if (file.getName().startsWith("Builds.xml")) {
if (file.getName().startsWith("Users.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element: dom.getRootElement().elements()) {
String userId = element.elementText("id").trim();
Element nameElement = element.element("name");
String name = nameElement.getText().trim();
if (userNames.add(name.toLowerCase()))
nameElement.setText(name.toLowerCase());
else
throw new ExplicitException("Duplicated login names found when convert '" + name + "' to lowercase");
if (userId.equals("-1")) {
element.addElement("fullName").setText("OneDev");
element.element("email").detach();
element.element("alternateEmails").detach();
} else if (userId.equals("-2")) {
element.addElement("fullName").setText("Unknown");
element.element("email").detach();
element.element("alternateEmails").detach();
} else {
Element emailElement = element.element("email");
String email = emailElement.getText().trim();
if (primaryEmails.put(email.toLowerCase(), userId) != null)
throw new ExplicitException("Duplicated email address found when convert '" + email + "' to lowercase");
emailElement.detach();
Element gitEmailElement = element.element("gitEmail");
if (gitEmailElement != null) {
String gitEmail = gitEmailElement.getText().trim();
gitEmails.put(gitEmail.toLowerCase(), userId);
gitEmailElement.detach();
}
Element alternateEmailsElement = element.element("alternateEmails");
for (Element alternateEmailElement: alternateEmailsElement.elements()) {
String alternateEmail = alternateEmailElement.getText().trim();
alternateEmails.put(alternateEmail.toLowerCase(), userId);
}
alternateEmailsElement.detach();
}
Element ssoInfoElement = element.element("ssoInfo");
Element connectorElement = ssoInfoElement.element("connector");
if (connectorElement != null)
element.addElement("ssoConnector").setText(connectorElement.getText().trim());
ssoInfoElement.detach();
}
dom.writeToFile(file, false);
} else if (file.getName().startsWith("Builds.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element: dom.getRootElement().elements())
element.element("triggerChain").setName("pipeline");
@ -3821,6 +3873,51 @@ public class DataMigrator {
dom.writeToFile(file, false);
}
}
VersionedXmlDoc emailAddressesDom;
File emailAddressesFile = new File(dataDir, "EmailAddresss.xml");
emailAddressesDom = new VersionedXmlDoc();
Element listElement = emailAddressesDom.addElement("list");
long id = 1;
Map<String, Element> primaryEmailElements = new HashMap<>();
for (Map.Entry<String, String> entry: primaryEmails.entrySet()) {
Element emailAddressElement = listElement.addElement("io.onedev.server.model.EmailAddress");
emailAddressElement.addAttribute("revision", "0.0");
emailAddressElement.addElement("id").setText(String.valueOf(id++));
emailAddressElement.addElement("primary").setText("true");
emailAddressElement.addElement("git").setText("true");
emailAddressElement.addElement("value").setText(entry.getKey());
emailAddressElement.addElement("owner").setText(entry.getValue());
primaryEmailElements.put(entry.getValue(), emailAddressElement);
}
for (Map.Entry<String, String> entry: gitEmails.entrySet()) {
if (!primaryEmails.containsKey(entry.getKey())) {
Element emailAddressElement = listElement.addElement("io.onedev.server.model.EmailAddress");
emailAddressElement.addAttribute("revision", "0.0");
emailAddressElement.addElement("id").setText(String.valueOf(id++));
emailAddressElement.addElement("primary").setText("false");
emailAddressElement.addElement("git").setText("true");
emailAddressElement.addElement("value").setText(entry.getKey());
emailAddressElement.addElement("owner").setText(entry.getValue());
primaryEmailElements.get(entry.getValue()).element("git").setText("false");
}
}
for (Map.Entry<String, String> entry: alternateEmails.entrySet()) {
if (!primaryEmails.containsKey(entry.getKey()) && !gitEmails.containsKey(entry.getKey())) {
Element emailAddressElement = listElement.addElement("io.onedev.server.model.EmailAddress");
emailAddressElement.addAttribute("revision", "0.0");
emailAddressElement.addElement("id").setText(String.valueOf(id++));
emailAddressElement.addElement("primary").setText("false");
emailAddressElement.addElement("git").setText("false");
emailAddressElement.addElement("value").setText(entry.getKey());
emailAddressElement.addElement("owner").setText(entry.getValue());
}
}
emailAddressesDom.writeToFile(emailAddressesFile, true);
}
}

View File

@ -0,0 +1,97 @@
package io.onedev.server.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.commons.lang3.RandomStringUtils;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.onedev.server.web.editable.annotation.Editable;
@Editable
@Entity
@Table(indexes={@Index(columnList="o_owner_id"), @Index(columnList="value")})
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class EmailAddress extends AbstractEntity {
private static final long serialVersionUID = 1L;
public static final String PROP_OWNER = "owner";
public static final String PROP_VALUE = "value";
@Column(nullable=false, unique=true)
private String value;
@JsonIgnore
private String verificationCode = RandomStringUtils.randomAlphanumeric(16);
private boolean primary;
private boolean git;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private User owner;
@Editable
@Email
@NotEmpty
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Editable
public String getVerficationCode() {
return verificationCode;
}
public void setVerificationCode(String verificationCode) {
this.verificationCode = verificationCode;
}
@Editable
public boolean isPrimary() {
return primary;
}
public void setPrimary(boolean primary) {
this.primary = primary;
}
@Editable
public boolean isGit() {
return git;
}
public void setGit(boolean git) {
this.git = git;
}
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
public boolean isVerified() {
return getVerficationCode() == null;
}
}

View File

@ -61,10 +61,10 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.LastCommitsOfChildren;
import org.eclipse.jgit.revwalk.LastCommitsOfChildren.Value;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.LastCommitsOfChildren.Value;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@ -91,11 +91,11 @@ import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.entitymanager.BuildQueryPersonalizationManager;
import io.onedev.server.entitymanager.CodeCommentQueryPersonalizationManager;
import io.onedev.server.entitymanager.CommitQueryPersonalizationManager;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.IssueQueryPersonalizationManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.PullRequestQueryPersonalizationManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.RefUpdated;
import io.onedev.server.git.BlameBlock;
import io.onedev.server.git.Blob;
@ -1477,11 +1477,11 @@ public class Project extends AbstractEntity {
cmd.range(range);
List<User> authors = new ArrayList<>();
UserManager userManager = OneDev.getInstance(UserManager.class);
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
for (BlameBlock block: cmd.call()) {
User author = userManager.find(block.getCommit().getAuthor());
if (author != null && !authors.contains(author))
authors.add(author);
EmailAddress emailAddress = emailAddressManager.findByPersonIdent(block.getCommit().getAuthor());
if (emailAddress != null && emailAddress.isVerified() && !authors.contains(emailAddress.getOwner()))
authors.add(emailAddress.getOwner());
}
return authors;

View File

@ -13,7 +13,6 @@ import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.ConstraintValidatorContext;
import org.apache.sshd.common.config.keys.KeyUtils;
@ -32,10 +31,7 @@ import io.onedev.server.web.editable.annotation.OmitName;
@Editable
@Entity
@Table(
indexes={@Index(columnList="o_owner_id"), @Index(columnList="digest")},
uniqueConstraints={@UniqueConstraint(columnNames={"digest"})}
)
@Table(indexes={@Index(columnList="o_owner_id"), @Index(columnList="digest")})
@ClassValidating
public class SshKey extends AbstractEntity implements Validatable {
@ -45,7 +41,7 @@ public class SshKey extends AbstractEntity implements Validatable {
private String content;
@JsonIgnore
@Column(nullable=false)
@Column(nullable=false, unique=true)
private String digest;
@JsonIgnore

View File

@ -1,27 +1,27 @@
package io.onedev.server.model;
import static io.onedev.server.model.User.PROP_ACCESS_TOKEN;
import static io.onedev.server.model.User.PROP_EMAIL;
import static io.onedev.server.model.User.PROP_FULL_NAME;
import static io.onedev.server.model.User.PROP_NAME;
import static io.onedev.server.model.User.PROP_SSO_CONNECTOR;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Lob;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.shiro.authc.AuthenticationInfo;
@ -31,19 +31,18 @@ import org.apache.shiro.subject.Subject;
import org.eclipse.jgit.lib.PersonIdent;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Sets;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.support.NamedProjectQuery;
import io.onedev.server.model.support.QueryPersonalization;
import io.onedev.server.model.support.SsoInfo;
import io.onedev.server.model.support.TwoFactorAuthentication;
import io.onedev.server.model.support.administration.authenticator.Authenticator;
import io.onedev.server.model.support.administration.sso.SsoConnector;
@ -52,7 +51,6 @@ import io.onedev.server.model.support.issue.NamedIssueQuery;
import io.onedev.server.model.support.pullrequest.NamedPullRequestQuery;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.match.MatchScoreUtils;
import io.onedev.server.util.validation.annotation.EmailList;
import io.onedev.server.util.validation.annotation.UserName;
import io.onedev.server.util.watch.QuerySubscriptionSupport;
import io.onedev.server.util.watch.QueryWatchSupport;
@ -61,10 +59,8 @@ import io.onedev.server.web.editable.annotation.Password;
@Entity
@Table(
indexes={@Index(columnList=PROP_NAME), @Index(columnList=PROP_EMAIL),
@Index(columnList=PROP_FULL_NAME), @Index(columnList=SsoInfo.COLUMN_CONNECTOR),
@Index(columnList=SsoInfo.COLUMN_SUBJECT), @Index(columnList=PROP_ACCESS_TOKEN)},
uniqueConstraints={@UniqueConstraint(columnNames={SsoInfo.COLUMN_CONNECTOR, SsoInfo.COLUMN_SUBJECT})})
indexes={@Index(columnList=PROP_NAME), @Index(columnList=PROP_FULL_NAME),
@Index(columnList=PROP_SSO_CONNECTOR), @Index(columnList=PROP_ACCESS_TOKEN)})
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
@Editable
public class User extends AbstractEntity implements AuthenticationInfo {
@ -87,26 +83,14 @@ public class User extends AbstractEntity implements AuthenticationInfo {
public static final String PROP_NAME = "name";
public static final String PROP_EMAIL = "email";
public static final String PROP_GIT_EMAIL = "gitEmail";
public static final String PROP_ALTERNATE_EMAILS = "alternateEmails";
public static final String PROP_PASSWORD = "password";
public static final String PROP_FULL_NAME = "fullName";
public static final String PROP_SSO_INFO = "ssoInfo";
public static final String PROP_SSO_CONNECTOR = "ssoConnector";
public static final String PROP_ACCESS_TOKEN = "accessToken";
public static final String AUTH_SOURCE_BUILTIN_USER_STORE = "Builtin User Store";
public static final String AUTH_SOURCE_EXTERNAL_AUTHENTICATOR = "External Authenticator";
public static final String AUTH_SOURCE_SSO_PROVIDER = "SSO Provider: ";
private static ThreadLocal<Stack<User>> stack = new ThreadLocal<Stack<User>>() {
@Override
@ -126,20 +110,10 @@ public class User extends AbstractEntity implements AuthenticationInfo {
private String fullName;
@JsonIgnore
@Embedded
private SsoInfo ssoInfo = new SsoInfo();
@Column(unique=true, nullable=false)
private String email;
@Column
private String gitEmail;
@Lob
@Column(nullable=false, length=1024)
private ArrayList<String> alternateEmails = new ArrayList<>();
private String ssoConnector;
@Column(unique=true, nullable=false)
@JsonIgnore
private String accessToken = RandomStringUtils.randomAlphanumeric(ACCESS_TOKEN_LEN);
@JsonIgnore
@ -189,6 +163,10 @@ public class User extends AbstractEntity implements AuthenticationInfo {
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
private Collection<SshKey> sshKeys = new ArrayList<>();
@OneToMany(mappedBy="owner", cascade=CascadeType.REMOVE)
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
private Collection<EmailAddress> emailAddresses = new ArrayList<>();
@JsonIgnore
@Lob
@Column(nullable=false, length=65535)
@ -226,6 +204,8 @@ public class User extends AbstractEntity implements AuthenticationInfo {
private transient Collection<Group> groups;
private transient List<EmailAddress> sortedEmailAddresses;
public QueryPersonalization<NamedProjectQuery> getProjectQueryPersonalization() {
return new QueryPersonalization<NamedProjectQuery>() {
@ -465,13 +445,14 @@ public class User extends AbstractEntity implements AuthenticationInfo {
public void setFullName(String fullName) {
this.fullName = fullName;
}
public SsoInfo getSsoInfo() {
return ssoInfo;
@Nullable
public String getSsoConnector() {
return ssoConnector;
}
public void setSsoInfo(SsoInfo ssoInfo) {
this.ssoInfo = ssoInfo;
public void setSsoConnector(String ssoConnector) {
this.ssoConnector = ssoConnector;
}
public String getAccessToken() {
@ -491,39 +472,6 @@ public class User extends AbstractEntity implements AuthenticationInfo {
this.twoFactorAuthentication = twoFactorAuthentication;
}
@Editable(order=300)
@NotEmpty
@Email
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Editable(order=350, description="Specify an email to use for web based git operations if you want to "
+ "keep your primary email secret")
public String getGitEmail() {
return gitEmail;
}
public void setGitEmail(String gitEmail) {
this.gitEmail = gitEmail;
}
@Editable(order=400, description="Optionally specify one or more alternate emails with one email per line. "
+ "With alternate emails, git commits authored/committed via your old emails can be associated with "
+ "your current account")
@EmailList
public ArrayList<String> getAlternateEmails() {
return alternateEmails;
}
public void setAlternateEmails(ArrayList<String> alternateEmails) {
this.alternateEmails = alternateEmails;
}
public Collection<Membership> getMemberships() {
return memberships;
}
@ -532,14 +480,6 @@ public class User extends AbstractEntity implements AuthenticationInfo {
this.memberships = memberships;
}
public Collection<String> getEmails() {
Collection<String> emails = Sets.newHashSet(email);
if (gitEmail != null)
emails.add(gitEmail);
emails.addAll(alternateEmails);
return emails;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -547,13 +487,17 @@ public class User extends AbstractEntity implements AuthenticationInfo {
.toString();
}
@Nullable
public PersonIdent asPerson() {
if (isSystem())
if (isSystem()) {
return new PersonIdent(getDisplayName(), "");
else if (getGitEmail() != null)
return new PersonIdent(getDisplayName(), getGitEmail());
else
return new PersonIdent(getDisplayName(), getEmail());
} else {
EmailAddress emailAddress = getGitEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
return new PersonIdent(getDisplayName(), emailAddress.getValue());
else
throw new ExplicitException("No verified email for git operations");
}
}
public String getDisplayName() {
@ -629,9 +573,17 @@ public class User extends AbstractEntity implements AuthenticationInfo {
this.sshKeys = sshKeys;
}
public boolean isSshKeyExternalManaged() {
public Collection<EmailAddress> getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Collection<EmailAddress> emailAddresses) {
this.emailAddresses = emailAddresses;
}
public boolean isSshKeyExternalManaged() {
if (isExternalManaged()) {
if (getSsoInfo().getConnector() != null) {
if (getSsoConnector() != null) {
return false;
} else {
Authenticator authenticator = OneDev.getInstance(SettingManager.class).getAuthenticator();
@ -645,9 +597,9 @@ public class User extends AbstractEntity implements AuthenticationInfo {
public boolean isMembershipExternalManaged() {
if (isExternalManaged()) {
SettingManager settingManager = OneDev.getInstance(SettingManager.class);
if (getSsoInfo().getConnector() != null) {
if (getSsoConnector() != null) {
SsoConnector ssoConnector = settingManager.getSsoConnectors().stream()
.filter(it->it.getName().equals(getSsoInfo().getConnector()))
.filter(it->it.getName().equals(getSsoConnector()))
.findFirst().orElse(null);
return ssoConnector != null && ssoConnector.isManagingMemberships();
} else {
@ -661,12 +613,12 @@ public class User extends AbstractEntity implements AuthenticationInfo {
public String getAuthSource() {
if (isExternalManaged()) {
if (getSsoInfo().getConnector() != null)
return AUTH_SOURCE_SSO_PROVIDER + getSsoInfo().getConnector();
if (getSsoConnector() != null)
return "SSO Provider: " + getSsoConnector();
else
return AUTH_SOURCE_EXTERNAL_AUTHENTICATOR;
return "External Authenticator";
} else {
return AUTH_SOURCE_BUILTIN_USER_STORE;
return "Builtin User Store";
}
}
@ -811,4 +763,40 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|| getGroups().stream().anyMatch(it->it.isEnforce2FA());
}
public List<EmailAddress> getSortedEmailAddresses() {
if (sortedEmailAddresses == null) {
sortedEmailAddresses = new ArrayList<>(getEmailAddresses());
Collections.sort(sortedEmailAddresses, new Comparator<EmailAddress>() {
@Override
public int compare(EmailAddress o1, EmailAddress o2) {
if (o1.isPrimary() && o2.isPrimary() || !o1.isPrimary() && !o2.isPrimary()) {
if (o1.isGit() && o2.isGit() || !o1.isGit() && !o2.isGit())
return o1.getId().compareTo(o2.getId());
else if (o1.isGit())
return -1;
else
return 1;
} else if (o1.isPrimary()) {
return -1;
} else {
return 1;
}
}
});
}
return sortedEmailAddresses;
}
@Nullable
public EmailAddress getPrimaryEmailAddress() {
return getSortedEmailAddresses().stream().filter(it->it.isPrimary()).findFirst().orElse(null);
}
@Nullable
public EmailAddress getGitEmailAddress() {
return getSortedEmailAddresses().stream().filter(it->it.isGit()).findFirst().orElse(null);
}
}

View File

@ -1,51 +0,0 @@
package io.onedev.server.model.support;
import java.io.Serializable;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class SsoInfo implements Serializable {
private static final long serialVersionUID = 1L;
public static final String COLUMN_CONNECTOR = "SSO_CONNECTOR";
public static final String COLUMN_SUBJECT = "SSO_SUBJECT";
public static final String PROP_CONNECTOR = "connector";
public static final String PROP_SUBJECT = "subject";
@Column(name=COLUMN_CONNECTOR)
private String connector;
/*
* SQL Server treats null as a value when checking unique constraints. So we
* need to populate subject even if no SSO is used to avoid violating unique
* constraint on connector and subject combo
*/
@Column(name=COLUMN_SUBJECT, nullable=false)
private String subject = UUID.randomUUID().toString();
@Nullable
public String getConnector() {
return connector;
}
public void setConnector(String connector) {
this.connector = connector;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
}

View File

@ -7,32 +7,24 @@ import javax.annotation.Nullable;
import org.apache.shiro.authc.AuthenticationToken;
import io.onedev.server.model.User;
import io.onedev.server.model.support.SsoInfo;
import io.onedev.server.model.support.administration.authenticator.Authenticated;
public class SsoAuthenticated extends Authenticated implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private final String subject;
private final String userName;
private final SsoConnector connector;
public SsoAuthenticated(String subject, String userName, String email, @Nullable String fullName,
public SsoAuthenticated(String userName, String email, @Nullable String fullName,
@Nullable Collection<String> groupNames, @Nullable Collection<String> sshKeys,
SsoConnector connector) {
super(email, fullName, groupNames, sshKeys);
this.subject = subject;
this.userName = userName;
this.connector = connector;
}
public String getSubject() {
return subject;
}
public String getUserName() {
return userName;
}
@ -51,10 +43,4 @@ public class SsoAuthenticated extends Authenticated implements AuthenticationTok
return User.EXTERNAL_MANAGED;
}
public SsoInfo getSsoInfo() {
SsoInfo ssoInfo = new SsoInfo();
ssoInfo.setConnector(connector.getName());
ssoInfo.setSubject(subject);
return ssoInfo;
}
}

View File

@ -50,9 +50,8 @@ public enum MergeStrategy {
ObjectId requestHead = request.getLatestUpdate().getHeadCommit();
ObjectId targetHead = request.getTarget().getObjectId();
PersonIdent committer = new PersonIdent(User.SYSTEM_NAME, "");
PersonIdent author = request.getSubmitter().asPerson();
return GitUtils.merge(repository, targetHead, requestHead, true, committer, author,
commitMessage, false);
return GitUtils.merge(repository, targetHead, requestHead, true, committer,
request.getSubmitter().asPerson(), commitMessage, false);
}
},

View File

@ -1,6 +1,7 @@
package io.onedev.server.notification;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@ -18,8 +19,10 @@ import io.onedev.server.event.issue.IssueEvent;
import io.onedev.server.event.pullrequest.PullRequestEvent;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.PullRequestAssignment;
import io.onedev.server.model.PullRequestReview;
import io.onedev.server.model.User;
import io.onedev.server.model.support.administration.notificationtemplate.NotificationTemplateSetting;
public abstract class AbstractNotificationManager {
@ -100,4 +103,12 @@ public abstract class AbstractNotificationManager {
return textBody.toString();
}
protected boolean isNotified(Collection<String> notifiedEmailAddresses, User user) {
for (EmailAddress emailAddress: user.getEmailAddresses()) {
if (emailAddress.isVerified() && notifiedEmailAddresses.contains(emailAddress.getValue()))
return true;
}
return false;
}
}

View File

@ -23,6 +23,7 @@ import io.onedev.server.event.build.BuildUpdated;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.BuildQueryPersonalization;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.NamedQuery;
@ -107,7 +108,9 @@ public class BuildNotificationManager extends AbstractNotificationManager {
User.push(user);
try {
if (BuildQuery.parse(event.getProject(), queryString, true, true).matches(build)) {
notifyEmails.add(user.getEmail());
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
notifyEmails.add(emailAddress.getValue());
break;
}
} catch (Exception e) {
@ -142,7 +145,9 @@ public class BuildNotificationManager extends AbstractNotificationManager {
User.push(user);
try {
if (BuildQuery.parse(null, queryString, true, true).matches(build)) {
notifyEmails.add(user.getEmail());
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
notifyEmails.add(emailAddress.getValue());
break;
}
} catch (Exception e) {

View File

@ -15,6 +15,7 @@ import io.onedev.server.event.codecomment.CodeCommentEvent;
import io.onedev.server.event.codecomment.CodeCommentReplied;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MentionParser;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.persistence.annotation.Transactional;
@ -63,11 +64,14 @@ public class CodeCommentNotificationManager extends AbstractNotificationManager
String threadingReferences = "<" + event.getComment().getProject().getPath()
+ "-codecomment-" + event.getComment().getId() + "@onedev>";
mailManager.sendMailAsync(Sets.newHashSet(user.getEmail()), Lists.newArrayList(),
Lists.newArrayList(), subject,
getHtmlBody(event, summary, processedMarkdown, url, false, null),
getTextBody(event, summary, markdown, url, false, null),
null, threadingReferences);
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Sets.newHashSet(emailAddress.getValue()), Lists.newArrayList(),
Lists.newArrayList(), subject,
getHtmlBody(event, summary, processedMarkdown, url, false, null),
getTextBody(event, summary, markdown, url, false, null),
null, threadingReferences);
}
}
}
}

View File

@ -24,6 +24,7 @@ import io.onedev.server.event.RefUpdated;
import io.onedev.server.git.GitUtils;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.CommitQueryPersonalization;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.NamedQuery;
@ -87,7 +88,9 @@ public class CommitNotificationManager extends AbstractNotificationManager {
User.push(user);
try {
if (CommitQuery.parse(project, queryString).matches(event)) {
notifyEmails.add(user.getEmail());
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
notifyEmails.add(emailAddress.getValue());
break;
}
} catch (Exception e) {

View File

@ -63,6 +63,7 @@ import io.onedev.commons.loader.Listen;
import io.onedev.commons.utils.ExceptionUtils;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.IssueWatchManager;
@ -77,6 +78,7 @@ import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.event.system.SystemStopping;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.IssueWatch;
@ -100,7 +102,7 @@ import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.security.permission.ProjectPermission;
import io.onedev.server.security.permission.ReadCode;
import io.onedev.server.util.EmailAddress;
import io.onedev.server.util.ParsedEmailAddress;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.util.validation.UserNameValidator;
@ -133,6 +135,8 @@ public class DefaultMailManager implements MailManager {
private final UserManager userManager;
private final EmailAddressManager emailAddressManager;
private final UrlManager urlManager;
private volatile Thread thread;
@ -144,7 +148,7 @@ public class DefaultMailManager implements MailManager {
IssueCommentManager issueCommentManager, IssueWatchManager issueWatchManager,
PullRequestManager pullRequestManager, PullRequestCommentManager pullRequestCommentManager,
PullRequestWatchManager pullRequestWatchManager, ExecutorService executorService,
UrlManager urlManager) {
UrlManager urlManager, EmailAddressManager emailAddressManager) {
this.transactionManager = transactionManager;
this.settingManager = setingManager;
this.userManager = userManager;
@ -158,6 +162,7 @@ public class DefaultMailManager implements MailManager {
this.pullRequestWatchManager = pullRequestWatchManager;
this.executorService = executorService;
this.urlManager = urlManager;
this.emailAddressManager = emailAddressManager;
}
@Sessional
@ -311,158 +316,172 @@ public class DefaultMailManager implements MailManager {
InternetAddress from = InternetAddress.parse(fromHeader[0], true)[0];
User user = userManager.findByEmail(from.getAddress());
EmailAddress fromAddressEntity = emailAddressManager.findByValue(from.getAddress());
if (fromAddressEntity != null && !fromAddressEntity.isVerified()) {
logger.error("Another account uses email address '{}' but not verified", from.getAddress());
} else {
User user = fromAddressEntity != null?fromAddressEntity.getOwner():null;
SenderAuthorization authorization = null;
String designatedProject = null;
ServiceDeskSetting serviceDeskSetting = settingManager.getServiceDeskSetting();
if (serviceDeskSetting != null) {
authorization = serviceDeskSetting.getSenderAuthorization(from.getAddress());
designatedProject = serviceDeskSetting.getDesignatedProject(from.getAddress());
}
ParsedEmailAddress parsedSystemAddress = ParsedEmailAddress.parse(mailSetting.getEmailAddress());
Collection<Issue> issues = new ArrayList<>();
Collection<PullRequest> pullRequests = new ArrayList<>();
Collection<InternetAddress> involved = new ArrayList<>();
SenderAuthorization authorization = null;
String designatedProject = null;
ServiceDeskSetting serviceDeskSetting = settingManager.getServiceDeskSetting();
if (serviceDeskSetting != null) {
authorization = serviceDeskSetting.getSenderAuthorization(from.getAddress());
designatedProject = serviceDeskSetting.getDesignatedProject(from.getAddress());
}
EmailAddress systemAddress = EmailAddress.parse(mailSetting.getEmailAddress());
Collection<Issue> issues = new ArrayList<>();
Collection<PullRequest> pullRequests = new ArrayList<>();
Collection<InternetAddress> involved = new ArrayList<>();
List<InternetAddress> receivers = new ArrayList<>();
receivers.addAll(Arrays.asList(InternetAddress.parse(toHeader[0], true)));
if (ccHeader != null && ccHeader.length != 0)
receivers.addAll(Arrays.asList(InternetAddress.parse(ccHeader[0], true)));
List<String> receiverEmailAddresses =
receivers.stream().map(it->it.getAddress()).collect(Collectors.toList());
for (InternetAddress receiver: receivers) {
EmailAddress receiverAddress = EmailAddress.parse(receiver.getAddress());
if (receiverAddress.toString().equals(systemAddress.toString())) {
if (serviceDeskSetting != null) {
if (designatedProject == null)
throw new ExplicitException("No project designated for sender: " + from.getAddress());
Project project = projectManager.findByPath(designatedProject);
if (project == null) {
String errorMessage = String.format(
"Sender project does not exist (sender: %s, project: %s)",
from.getAddress(), designatedProject);
throw new ExplicitException(errorMessage);
}
checkPermission(from, project, new AccessProject(), user, authorization);
issues.add(openIssue(message, project, from, user, authorization));
} else {
throw new ExplicitException("Unable to create issue from email as service desk is not enabled");
}
} else if (receiverAddress.getDomain().equals(systemAddress.getDomain())
&& receiverAddress.getName().startsWith(systemAddress.getName() + "+")) {
String subAddress = receiverAddress.getName().substring(systemAddress.getName().length()+1);
if (subAddress.equals(MailManager.TEST_SUB_ADDRESS)) {
continue;
} else if (subAddress.contains("~")) {
Long entityId;
try {
entityId = Long.parseLong(StringUtils.substringAfter(subAddress, "~"));
} catch (NumberFormatException e) {
throw new ExplicitException("Invalid id specified in receipient address: " + receiverAddress);
}
if (subAddress.contains("issue")) {
Issue issue = issueManager.get(entityId);
if (issue == null)
throw new ExplicitException("Non-existent issue specified in receipient address: " + receiverAddress);
if (subAddress.contains("unsubscribe")) {
if (user != null) {
IssueWatch watch = issueWatchManager.find(issue, user);
if (watch != null) {
watch.setWatching(false);
issueWatchManager.save(watch);
String subject = "Unsubscribed successfully from issue " + issue.getFQN();
String body = "You will no longer receive notifications of issue " + issue.getFQN() + " unless mentioned. "
+ "However if you subscribed to certain issue queries, you may still get notifications of newly "
+ "created issues matching those queries. In this case, you will need to login to your account "
+ "and unsubscribe those queries.";
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
subject, body, body, null, getMessageId(message));
}
}
} else {
checkPermission(from, issue.getProject(), new AccessProject(), user, authorization);
addComment(issue, message, from, receiverEmailAddresses, user, authorization);
issues.add(issue);
}
} else if (subAddress.contains("pullrequest")) {
PullRequest pullRequest = pullRequestManager.get(entityId);
if (pullRequest == null)
throw new ExplicitException("Non-existent pull request specified in receipient address: " + receiverAddress);
if (subAddress.contains("unsubscribe")) {
if (user != null) {
PullRequestWatch watch = pullRequestWatchManager.find(pullRequest, user);
if (watch != null) {
watch.setWatching(false);
pullRequestWatchManager.save(watch);
String subject = "Unsubscribed successfully from pull request " + pullRequest.getFQN();
String body = "You will no longer receive notifications of pull request " + pullRequest.getFQN()
+ " unless mentioned. However if you subscribed to certain pull request queries, you may still "
+ "get notifications of newly submitted pull request matching those queries. In this case, you "
+ "will need to login to your account and unsubscribe those queries.";
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
subject, body, body, null, getMessageId(message));
}
}
} else {
checkPermission(from, pullRequest.getTargetProject(), new ReadCode(), user, authorization);
addComment(pullRequest, message, from, receiverEmailAddresses, user, authorization);
pullRequests.add(pullRequest);
}
} else {
throw new ExplicitException("Invalid receipient address: " + receiverAddress);
}
} else {
Project project = projectManager.findByServiceDeskName(subAddress);
if (project == null)
project = projectManager.findByPath(subAddress);
if (project == null)
throw new ExplicitException("Non-existent project specified in receipient address: " + receiverAddress);
List<InternetAddress> receivers = new ArrayList<>();
receivers.addAll(Arrays.asList(InternetAddress.parse(toHeader[0], true)));
if (ccHeader != null && ccHeader.length != 0)
receivers.addAll(Arrays.asList(InternetAddress.parse(ccHeader[0], true)));
List<String> receiverEmailAddresses =
receivers.stream().map(it->it.getAddress()).collect(Collectors.toList());
for (InternetAddress receiver: receivers) {
ParsedEmailAddress parsedReceiverAddress = ParsedEmailAddress.parse(receiver.getAddress());
if (parsedReceiverAddress.toString().equals(parsedSystemAddress.toString())) {
if (serviceDeskSetting != null) {
if (designatedProject == null)
throw new ExplicitException("No project designated for sender: " + from.getAddress());
Project project = projectManager.findByPath(designatedProject);
if (project == null) {
String errorMessage = String.format(
"Sender project does not exist (sender: %s, project: %s)",
from.getAddress(), designatedProject);
throw new ExplicitException(errorMessage);
}
checkPermission(from, project, new AccessProject(), user, authorization);
logger.debug("Creating issue via email (project: {})...", project.getPath());
issues.add(openIssue(message, project, from, user, authorization));
} else {
throw new ExplicitException("Unable to create issue from email as service desk is not enabled");
}
}
} else {
involved.add(receiver);
}
}
for (Issue issue: issues) {
for (InternetAddress each: involved) {
user = userManager.findByEmail(each.getAddress());
if (serviceDeskSetting != null)
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
try {
checkPermission(each, issue.getProject(), new AccessProject(), user, authorization);
if (user == null)
user = createUserIfNotExist(each, issue.getProject(), authorization.getAuthorizedRole());
watch(user, issue);
} catch (UnauthorizedException e) {
logger.error("Error adding receipient to watch list", e);
} else if (parsedReceiverAddress.getDomain().equals(parsedSystemAddress.getDomain())
&& parsedReceiverAddress.getName().startsWith(parsedSystemAddress.getName() + "+")) {
String subAddress = parsedReceiverAddress.getName().substring(parsedSystemAddress.getName().length()+1);
if (subAddress.equals(MailManager.TEST_SUB_ADDRESS)) {
continue;
} else if (subAddress.contains("~")) {
Long entityId;
try {
entityId = Long.parseLong(StringUtils.substringAfter(subAddress, "~"));
} catch (NumberFormatException e) {
throw new ExplicitException("Invalid id specified in receipient address: " + parsedReceiverAddress);
}
if (subAddress.contains("issue")) {
Issue issue = issueManager.get(entityId);
if (issue == null)
throw new ExplicitException("Non-existent issue specified in receipient address: " + parsedReceiverAddress);
if (subAddress.contains("unsubscribe")) {
if (user != null) {
IssueWatch watch = issueWatchManager.find(issue, user);
if (watch != null) {
watch.setWatching(false);
issueWatchManager.save(watch);
String subject = "Unsubscribed successfully from issue " + issue.getFQN();
String body = "You will no longer receive notifications of issue " + issue.getFQN() + " unless mentioned. "
+ "However if you subscribed to certain issue queries, you may still get notifications of newly "
+ "created issues matching those queries. In this case, you will need to login to your account "
+ "and unsubscribe those queries.";
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
subject, body, body, null, getMessageId(message));
}
}
} else {
checkPermission(from, issue.getProject(), new AccessProject(), user, authorization);
addComment(issue, message, from, receiverEmailAddresses, user, authorization);
issues.add(issue);
}
} else if (subAddress.contains("pullrequest")) {
PullRequest pullRequest = pullRequestManager.get(entityId);
if (pullRequest == null)
throw new ExplicitException("Non-existent pull request specified in receipient address: " + parsedReceiverAddress);
if (subAddress.contains("unsubscribe")) {
if (user != null) {
PullRequestWatch watch = pullRequestWatchManager.find(pullRequest, user);
if (watch != null) {
watch.setWatching(false);
pullRequestWatchManager.save(watch);
String subject = "Unsubscribed successfully from pull request " + pullRequest.getFQN();
String body = "You will no longer receive notifications of pull request " + pullRequest.getFQN()
+ " unless mentioned. However if you subscribed to certain pull request queries, you may still "
+ "get notifications of newly submitted pull request matching those queries. In this case, you "
+ "will need to login to your account and unsubscribe those queries.";
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
subject, body, body, null, getMessageId(message));
}
}
} else {
checkPermission(from, pullRequest.getTargetProject(), new ReadCode(), user, authorization);
addComment(pullRequest, message, from, receiverEmailAddresses, user, authorization);
pullRequests.add(pullRequest);
}
} else {
throw new ExplicitException("Invalid receipient address: " + parsedReceiverAddress);
}
} else {
Project project = projectManager.findByServiceDeskName(subAddress);
if (project == null)
project = projectManager.findByPath(subAddress);
if (project == null)
throw new ExplicitException("Non-existent project specified in receipient address: " + parsedReceiverAddress);
if (serviceDeskSetting != null) {
checkPermission(from, project, new AccessProject(), user, authorization);
logger.debug("Creating issue via email (project: {})...", project.getPath());
issues.add(openIssue(message, project, from, user, authorization));
} else {
throw new ExplicitException("Unable to create issue from email as service desk is not enabled");
}
}
} else {
involved.add(receiver);
}
}
}
for (PullRequest pullRequest: pullRequests) {
for (InternetAddress each: involved) {
user = userManager.findByEmail(each.getAddress());
if (serviceDeskSetting != null)
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
try {
checkPermission(each, pullRequest.getProject(), new ReadCode(), user, authorization);
if (user == null)
user = createUserIfNotExist(each, pullRequest.getProject(), authorization.getAuthorizedRole());
watch(user, pullRequest);
} catch (UnauthorizedException e) {
logger.error("Error adding receipient to watch list", e);
for (Issue issue: issues) {
for (InternetAddress each: involved) {
EmailAddress emailAddressEntity = emailAddressManager.findByValue(each.getAddress());
if (emailAddressEntity != null && !emailAddressEntity.isVerified()) {
logger.error("Another account uses email address '{}' but not verified", each.getAddress());
} else {
if (serviceDeskSetting != null)
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
user = emailAddressEntity != null?emailAddressEntity.getOwner():null;
try {
checkPermission(each, issue.getProject(), new AccessProject(), user, authorization);
if (user == null)
user = createUser(each, issue.getProject(), authorization.getAuthorizedRole());
watch(user, issue);
} catch (UnauthorizedException e) {
logger.error("Error adding receipient to watch list", e);
}
}
}
}
for (PullRequest pullRequest: pullRequests) {
for (InternetAddress each: involved) {
EmailAddress emailAddressEntity = emailAddressManager.findByValue(each.getAddress());
if (emailAddressEntity != null && !emailAddressEntity.isVerified()) {
logger.error("Another account uses email address '{}' but not verified", each.getAddress());
} else {
user = emailAddressEntity != null?emailAddressEntity.getOwner():null;
if (serviceDeskSetting != null)
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
try {
checkPermission(each, pullRequest.getProject(), new ReadCode(), user, authorization);
if (user == null)
user = createUser(each, pullRequest.getProject(), authorization.getAuthorizedRole());
watch(user, pullRequest);
} catch (UnauthorizedException e) {
logger.error("Error adding receipient to watch list", e);
}
}
}
}
}
@ -545,7 +564,7 @@ public class DefaultMailManager implements MailManager {
IssueComment comment = new IssueComment();
comment.setIssue(issue);
if (user == null)
user = createUserIfNotExist(author, issue.getProject(), authorization.getAuthorizedRole());
user = createUser(author, issue.getProject(), authorization.getAuthorizedRole());
comment.setUser(user);
String content = stripQuotation(readText(issue.getProject(), issue.getUUID(), message));
if (content != null) {
@ -560,7 +579,7 @@ public class DefaultMailManager implements MailManager {
PullRequestComment comment = new PullRequestComment();
comment.setRequest(pullRequest);
if (user == null)
user = createUserIfNotExist(author, pullRequest.getProject(), authorization.getAuthorizedRole());
user = createUser(author, pullRequest.getProject(), authorization.getAuthorizedRole());
comment.setUser(user);
String content = stripQuotation(readText(pullRequest.getProject(), pullRequest.getUUID(), message));
if (content != null) {
@ -596,7 +615,7 @@ public class DefaultMailManager implements MailManager {
issue.setDescription(description);
if (user == null)
user = createUserIfNotExist(submitter, project, authorization.getAuthorizedRole());
user = createUser(submitter, project, authorization.getAuthorizedRole());
issue.setSubmitter(user);
GlobalIssueSetting issueSetting = settingManager.getIssueSetting();
@ -622,17 +641,21 @@ public class DefaultMailManager implements MailManager {
issue.getEffectiveThreadingReference());
return issue;
}
private User createUserIfNotExist(InternetAddress address, Project project, Role role) {
User user = userManager.findByEmail(address.getAddress());
if (user == null) {
user = new User();
user.setName(UserNameValidator.suggestUserName(EmailAddress.parse(address.getAddress()).getName()));
user.setEmail(address.getAddress());
user.setFullName(address.getPersonal());
user.setPassword("impossible password");
userManager.save(user);
}
private User createUser(InternetAddress address, Project project, Role role) {
User user = new User();
user.setName(UserNameValidator.suggestUserName(ParsedEmailAddress.parse(address.getAddress()).getName()));
user.setFullName(address.getPersonal());
user.setPassword("impossible password");
userManager.save(user);
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue(address.getAddress());
emailAddress.setVerificationCode(null);
emailAddress.setPrimary(true);
emailAddress.setGit(true);
emailAddress.setOwner(user);
emailAddressManager.save(emailAddress);
boolean found = false;
for (UserAuthorization authorization: user.getAuthorizations()) {
@ -648,6 +671,7 @@ public class DefaultMailManager implements MailManager {
authorization.setRole(role);
authorizationManager.save(authorization);
}
return user;
}
@ -854,7 +878,7 @@ public class DefaultMailManager implements MailManager {
public String getReplyAddress(Issue issue) {
MailSetting mailSetting = settingManager.getMailSetting();
if (mailSetting != null && mailSetting.getReceiveMailSetting() != null) {
EmailAddress systemAddress = EmailAddress.parse(mailSetting.getEmailAddress());
ParsedEmailAddress systemAddress = ParsedEmailAddress.parse(mailSetting.getEmailAddress());
return systemAddress.getSubAddressed("issue~" + issue.getId());
} else {
return null;
@ -865,7 +889,7 @@ public class DefaultMailManager implements MailManager {
public String getReplyAddress(PullRequest request) {
MailSetting mailSetting = settingManager.getMailSetting();
if (mailSetting != null && mailSetting.getReceiveMailSetting() != null) {
EmailAddress systemAddress = EmailAddress.parse(mailSetting.getEmailAddress());
ParsedEmailAddress systemAddress = ParsedEmailAddress.parse(mailSetting.getEmailAddress());
return systemAddress.getSubAddressed("pullrequest~" + request.getId());
} else {
return null;
@ -876,7 +900,7 @@ public class DefaultMailManager implements MailManager {
public String getUnsubscribeAddress(Issue issue) {
MailSetting mailSetting = settingManager.getMailSetting();
if (mailSetting != null && mailSetting.getReceiveMailSetting() != null) {
EmailAddress systemAddress = EmailAddress.parse(mailSetting.getEmailAddress());
ParsedEmailAddress systemAddress = ParsedEmailAddress.parse(mailSetting.getEmailAddress());
return systemAddress.getSubAddressed("issueunsubscribe~" + issue.getId());
} else {
return null;
@ -887,7 +911,7 @@ public class DefaultMailManager implements MailManager {
public String getUnsubscribeAddress(PullRequest request) {
MailSetting mailSetting = settingManager.getMailSetting();
if (mailSetting != null && mailSetting.getReceiveMailSetting() != null) {
EmailAddress systemAddress = EmailAddress.parse(mailSetting.getEmailAddress());
ParsedEmailAddress systemAddress = ParsedEmailAddress.parse(mailSetting.getEmailAddress());
return systemAddress.getSubAddressed("pullrequestunsubscribe~" + request.getId());
} else {
return null;

View File

@ -28,6 +28,7 @@ import io.onedev.server.event.issue.IssueOpened;
import io.onedev.server.infomanager.UserInfoManager;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MentionParser;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Group;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueWatch;
@ -156,11 +157,14 @@ public class IssueNotificationManager extends AbstractNotificationManager {
String threadingReferences = String.format("<you-in-field-%s-%s@onedev>", entry.getKey(), issue.getUUID());
for (User member: entry.getValue().getMembers()) {
if (!member.equals(user)) {
mailManager.sendMailAsync(Sets.newHashSet(member.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = member.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Sets.newHashSet(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
}
}
@ -175,11 +179,14 @@ public class IssueNotificationManager extends AbstractNotificationManager {
String threadingReferences = String.format("<you-in-field-%s-%s@onedev>", entry.getKey(), issue.getUUID());
for (User member: entry.getValue()) {
if (!member.equals(user)) {
mailManager.sendMailAsync(Sets.newHashSet(member.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = member.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Sets.newHashSet(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
}
}
@ -199,15 +206,18 @@ public class IssueNotificationManager extends AbstractNotificationManager {
User mentionedUser = userManager.findByName(userName);
if (mentionedUser != null) {
issueWatchManager.watch(issue, mentionedUser, true);
if (!notifiedEmailAddresses.stream().anyMatch(mentionedUser.getEmails()::contains)) {
if (!isNotified(notifiedEmailAddresses, mentionedUser)) {
String subject = String.format("[Issue %s] (Mentioned You) %s", issue.getFQN(), issue.getTitle());
String threadingReferences = String.format("<mentioned-%s@onedev>", issue.getUUID());
mailManager.sendMailAsync(Sets.newHashSet(mentionedUser.getEmail()),
Sets.newHashSet(), Sets.newHashSet(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = mentionedUser.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Sets.newHashSet(emailAddress.getValue()),
Sets.newHashSet(), Sets.newHashSet(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
notifiedUsers.add(mentionedUser);
}
}
@ -216,19 +226,21 @@ public class IssueNotificationManager extends AbstractNotificationManager {
if (!(event instanceof IssueChanged)
|| !(((IssueChanged) event).getChange().getData() instanceof ReferencedFromAware)) {
Collection<User> bccUsers = new HashSet<>();
Collection<String> bccEmailAddresses = new HashSet<>();
for (IssueWatch watch: issue.getWatches()) {
Date visitDate = userInfoManager.getIssueVisitDate(watch.getUser(), issue);
if (watch.isWatching()
&& (visitDate == null || visitDate.before(event.getDate()))
&& !notifiedUsers.contains(watch.getUser())
&& !notifiedEmailAddresses.stream().anyMatch(watch.getUser().getEmails()::contains)) {
bccUsers.add(watch.getUser());
&& !isNotified(notifiedEmailAddresses, watch.getUser())) {
EmailAddress emailAddress = watch.getUser().getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
bccEmailAddresses.add(emailAddress.getValue());
}
}
if (!bccUsers.isEmpty()) {
if (!bccEmailAddresses.isEmpty()) {
String subject = String.format("[Issue %s] (%s) %s",
issue.getFQN(), (event instanceof IssueOpened)?"Opened":"Updated", issue.getTitle());
@ -238,10 +250,10 @@ public class IssueNotificationManager extends AbstractNotificationManager {
String threadingReferences = issue.getEffectiveThreadingReference();
mailManager.sendMailAsync(Sets.newHashSet(), Sets.newHashSet(),
bccUsers.stream().map(User::getEmail).collect(Collectors.toList()),
subject, htmlBody, textBody, replyAddress, threadingReferences);
bccEmailAddresses, subject, htmlBody, textBody,
replyAddress, threadingReferences);
}
}
}
}

View File

@ -34,6 +34,7 @@ import io.onedev.server.event.pullrequest.PullRequestUpdated;
import io.onedev.server.infomanager.UserInfoManager;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MentionParser;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestAssignment;
import io.onedev.server.model.PullRequestReview;
@ -201,11 +202,14 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
WordUtils.capitalize(changeData.getActivity()), request.getTitle());
String threadingReferences = String.format("<%s-%s@onedev>",
changeData.getActivity().replace(' ', '-'), request.getUUID());
mailManager.sendMailAsync(Lists.newArrayList(request.getSubmitter().getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = request.getSubmitter().getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Lists.newArrayList(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
notifiedUsers.add(request.getSubmitter());
}
} else if (changeData instanceof PullRequestAssigneeAddData) {
@ -232,11 +236,14 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
assignmentSummary = user.getDisplayName() + " assigned to you";
else
assignmentSummary = "Assigned to you";
mailManager.sendMailAsync(Lists.newArrayList(assignee.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, assignmentSummary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, assignmentSummary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = assignee.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Lists.newArrayList(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, assignmentSummary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, assignmentSummary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
notifiedUsers.add(assignee);
}
}
@ -252,11 +259,15 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
reviewInvitationSummary = user.getDisplayName() + " invited you to review";
else
reviewInvitationSummary = "Invited you to review";
mailManager.sendMailAsync(Lists.newArrayList(reviewer.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, reviewInvitationSummary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, reviewInvitationSummary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = reviewer.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Lists.newArrayList(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, reviewInvitationSummary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, reviewInvitationSummary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
notifiedUsers.add(reviewer);
}
}
@ -272,15 +283,18 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
User mentionedUser = userManager.findByName(userName);
if (mentionedUser != null) {
pullRequestWatchManager.watch(request, mentionedUser, true);
if (!notifiedEmailAddresses.stream().anyMatch(mentionedUser.getEmails()::contains)) {
if (!isNotified(notifiedEmailAddresses, mentionedUser)) {
String subject = String.format("[Pull Request %s] (Mentioned You) %s", request.getFQN(), request.getTitle());
String threadingReferences = String.format("<mentioned-%s@onedev>", request.getUUID());
mailManager.sendMailAsync(Sets.newHashSet(mentionedUser.getEmail()),
Sets.newHashSet(), Sets.newHashSet(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
EmailAddress emailAddress = mentionedUser.getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified()) {
mailManager.sendMailAsync(Sets.newHashSet(emailAddress.getValue()),
Sets.newHashSet(), Sets.newHashSet(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
notifiedUsers.add(mentionedUser);
}
}
@ -302,7 +316,7 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
}
if (notifyWatchers) {
Collection<User> bccUsers = new HashSet<>();
Collection<String> bccEmailAddresses = new HashSet<>();
for (PullRequestWatch watch: request.getWatches()) {
Date visitDate = userInfoManager.getPullRequestVisitDate(watch.getUser(), request);
@ -310,12 +324,14 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
&& (visitDate == null || visitDate.before(event.getDate()))
&& (!(event instanceof PullRequestUpdated) || !watch.getUser().equals(request.getSubmitter()))
&& !notifiedUsers.contains(watch.getUser())
&& !notifiedEmailAddresses.stream().anyMatch(watch.getUser().getEmails()::contains)) {
bccUsers.add(watch.getUser());
&& !isNotified(notifiedEmailAddresses, watch.getUser())) {
EmailAddress emailAddress = watch.getUser().getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
bccEmailAddresses.add(emailAddress.getValue());
}
}
if (!bccUsers.isEmpty()) {
if (!bccEmailAddresses.isEmpty()) {
String subject = String.format("[Pull Request %s] (%s) %s",
request.getFQN(), (event instanceof PullRequestOpened)?"Opened":"Updated", request.getTitle());
String threadingReferences = "<" + request.getUUID() + "@onedev>";
@ -324,8 +340,8 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
String textBody = getTextBody(event, summary, event.getTextBody(), url, replyable, unsubscribable);
mailManager.sendMailAsync(
Lists.newArrayList(), Lists.newArrayList(),
bccUsers.stream().map(User::getEmail).collect(Collectors.toList()),
subject, htmlBody, textBody, replyAddress, threadingReferences);
bccEmailAddresses, subject, htmlBody, textBody,
replyAddress, threadingReferences);
}
}
}

View File

@ -0,0 +1,197 @@
package io.onedev.server.rest;
import java.io.Serializable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.rest.annotation.EntityCreate;
import io.onedev.server.security.SecurityUtils;
@Api(order=5010)
@Path("/email-addresses")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Singleton
public class EmailAddressResource {
private final UserManager userManager;
private final EmailAddressManager emailAddressManager;
private final SettingManager settingManager;
@Inject
public EmailAddressResource(UserManager userManager, EmailAddressManager emailAddressManager,
SettingManager settingManager) {
this.userManager = userManager;
this.emailAddressManager = emailAddressManager;
this.settingManager = settingManager;
}
@Api(order=100)
@Path("/{emailAddressId}")
@GET
public EmailAddress get(@PathParam("emailAddressId") Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
return emailAddress;
}
@Api(order=150)
@Path("/{emailAddressId}/verified")
@GET
public boolean getVerified(@PathParam("emailAddressId") Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
return emailAddress.isVerified();
}
@Api(order=200, description="Create new email address")
@POST
public Long create(@NotNull @Valid EmailAddressCreateData data) {
User user = userManager.load(data.getOwnerId());
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
if (emailAddressManager.findByValue(data.getValue()) != null)
throw new ExplicitException("This email address is already used by another user");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setOwner(userManager.load(data.getOwnerId()));
emailAddress.setValue(data.getValue());
if (SecurityUtils.isAdministrator())
emailAddress.setVerificationCode(null);
emailAddressManager.save(emailAddress);
return emailAddress.getId();
}
@Api(order=250, description="Set as primary email address")
@Path("/primary")
@POST
public Long setAsPrimary(@NotNull Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
if (emailAddress.getOwner().isExternalManaged())
throw new ExplicitException("Can not set primary email address for externally authenticated user");
emailAddressManager.setAsPrimary(emailAddress);
return emailAddressId;
}
@Api(order=260, description="Use for git operations")
@Path("/git")
@POST
public Long useForGitOperations(@NotNull Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
emailAddressManager.useForGitOperations(emailAddress);
return emailAddressId;
}
@Api(order=260, description="Resend verification email")
@Path("/resend-verification-email")
@POST
public Long resendVerificationEmail(@NotNull Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (!SecurityUtils.isAdministrator() && !emailAddress.getOwner().equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
if (settingManager.getMailSetting() == null)
throw new ExplicitException("Unable to send verification email as mail setting is not specified");
if (emailAddress.isVerified())
throw new ExplicitException("Unable to send verification email as this email address is already verified");
emailAddressManager.sendVerificationEmail(emailAddress);
return emailAddressId;
}
@Api(order=300)
@Path("/{emailAddressId}")
@DELETE
public Response delete(@PathParam("emailAddressId") Long emailAddressId) {
EmailAddress emailAddress = emailAddressManager.load(emailAddressId);
if (SecurityUtils.isAdministrator()
|| emailAddress.getOwner().equals(SecurityUtils.getUser())) {
if (emailAddress.isPrimary() && emailAddress.getOwner().isExternalManaged()) {
throw new ExplicitException("Can not delete primary email address of "
+ "externally authenticated user");
}
if (emailAddress.getOwner().getEmailAddresses().size() == 1)
throw new ExplicitException("At least one email address should be present for a user");
emailAddressManager.delete(emailAddress);
return Response.ok().build();
} else {
throw new UnauthorizedException();
}
}
@EntityCreate(EmailAddress.class)
public static class EmailAddressCreateData implements Serializable {
private static final long serialVersionUID = 1L;
private Long ownerId;
private String value;
@Api(order=100, description="Id of user owning this email address")
@NotNull
public Long getOwnerId() {
return ownerId;
}
public void setOwnerId(Long ownerId) {
this.ownerId = ownerId;
}
@Api(order=200, description="The email address string")
@NotEmpty
@Email
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -20,7 +20,7 @@ import io.onedev.server.model.IssueQueryPersonalization;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils;
@Api(order=5100)
@Api(order=5150)
@Path("/issue-query-personalizations")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)

View File

@ -203,8 +203,8 @@ public class RepositoryResource {
if (project.getTagRef(request.getTagName()) != null) {
throw new InvalidParamException("Tag '" + request.getTagName() + "' already exists");
} else {
PersonIdent tagger = SecurityUtils.getUser().asPerson();
project.createTag(request.getTagName(), request.getRevision(), tagger, request.getTagMessage());
project.createTag(request.getTagName(), request.getRevision(),
SecurityUtils.getUser().asPerson(), request.getTagMessage());
}
return Response.ok().build();

View File

@ -21,7 +21,7 @@ import io.onedev.server.model.SshKey;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils;
@Api(order=5100)
@Api(order=5050)
@Path("/ssh-keys")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)

View File

@ -7,10 +7,10 @@ import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@ -26,18 +26,17 @@ import javax.ws.rs.core.Response;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import com.google.common.collect.Sets;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.SshKeyManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.BuildQueryPersonalization;
import io.onedev.server.model.CodeCommentQueryPersonalization;
import io.onedev.server.model.CommitQueryPersonalization;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.IssueQueryPersonalization;
import io.onedev.server.model.IssueVote;
import io.onedev.server.model.IssueWatch;
@ -50,15 +49,15 @@ import io.onedev.server.model.SshKey;
import io.onedev.server.model.User;
import io.onedev.server.model.UserAuthorization;
import io.onedev.server.model.support.NamedProjectQuery;
import io.onedev.server.model.support.SsoInfo;
import io.onedev.server.model.support.build.NamedBuildQuery;
import io.onedev.server.model.support.issue.NamedIssueQuery;
import io.onedev.server.model.support.pullrequest.NamedPullRequestQuery;
import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.rest.annotation.EntityCreate;
import io.onedev.server.rest.exception.InvalidParamException;
import io.onedev.server.rest.support.RestConstants;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.validation.annotation.UserName;
@Api(order=5000)
@Path("/users")
@ -73,17 +72,21 @@ public class UserResource {
private final PasswordService passwordService;
private final EmailAddressManager emailAddressManager;
@Inject
public UserResource(UserManager userManager, SshKeyManager sshKeyManager, PasswordService passwordService) {
public UserResource(UserManager userManager, SshKeyManager sshKeyManager,
PasswordService passwordService, EmailAddressManager emailAddressManager) {
this.userManager = userManager;
this.sshKeyManager = sshKeyManager;
this.passwordService = passwordService;
this.emailAddressManager = emailAddressManager;
}
@Api(order=100)
@Path("/{userId}")
@GET
public User getBasicInfo(@PathParam("userId") Long userId) {
public User getProfile(@PathParam("userId") Long userId) {
User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
@ -93,13 +96,33 @@ public class UserResource {
@Api(order=200)
@Path("/me")
@GET
public User getMyBasicInfo() {
public User getMyProfile() {
User user = SecurityUtils.getUser();
if (user == null)
throw new UnauthenticatedException();
return user;
}
@Api(order=250)
@Path("/{userId}/access-token")
@GET
public String getAccessToken(@PathParam("userId") Long userId) {
User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
return user.getAccessToken();
}
@Api(order=275)
@Path("/{userId}/email-addresses")
@GET
public Collection<EmailAddress> getEmailAddresses(@PathParam("userId") Long userId) {
User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
return user.getEmailAddresses();
}
@Api(order=300)
@Path("/{userId}/authorizations")
@GET
@ -118,16 +141,6 @@ public class UserResource {
return userManager.load(userId).getMemberships();
}
@Api(order=500)
@Path("/{userId}/sso-info")
@GET
public SsoInfo getSsoInfo(@PathParam("userId") Long userId) {
User user = userManager.load(userId);
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
throw new UnauthorizedException();
return user.getSsoInfo();
}
@Api(order=600)
@Path("/{userId}/pull-request-reviews")
@GET
@ -258,8 +271,9 @@ public class UserResource {
@Api(order=1800)
@GET
public List<User> queryBasicInfo(@QueryParam("name") String name, @QueryParam("fullName") String fullName,
@QueryParam("email") String email, @QueryParam("offset") @Api(example="0") int offset,
public List<User> queryProfile(
@QueryParam("term") @Api(description="Any string in login name, full name or email address") String term,
@QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) {
if (!SecurityUtils.isAdministrator())
@ -268,51 +282,56 @@ public class UserResource {
if (count > RestConstants.MAX_PAGE_SIZE)
throw new InvalidParamException("Count should not be greater than " + RestConstants.MAX_PAGE_SIZE);
EntityCriteria<User> criteria = EntityCriteria.of(User.class);
criteria.add(Restrictions.gt("id", 0L));
if (name != null)
criteria.add(Restrictions.ilike("name", name.replace('*', '%'), MatchMode.EXACT));
if (fullName != null)
criteria.add(Restrictions.ilike("fullName", fullName.replace('*', '%'), MatchMode.EXACT));
if (email != null)
criteria.add(Restrictions.ilike("email", email.replace('*', '%'), MatchMode.EXACT));
return userManager.query(criteria, offset, count);
return userManager.query(term, offset, count);
}
@Api(order=1900, description="Update user of specified id in request body, or create new if id property not provided")
@Api(order=1900, description="Create new user")
@POST
public Long createOrUpdate(@NotNull User user) {
if (user.isNew()) {
if (!SecurityUtils.isAdministrator()) {
throw new UnauthorizedException();
} else {
user.setPassword("impossible_password");
checkEmails(user);
userManager.save(user);
}
} else {
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser())) {
throw new UnauthorizedException();
} else {
checkEmails(user);
userManager.save(user, (String) user.getCustomData());
}
}
return user.getId();
public Long create(@NotNull @Valid UserCreateData data) {
if (SecurityUtils.isAdministrator()) {
if (userManager.findByName(data.getName()) != null)
throw new ExplicitException("Login name is already used by another user");
if (emailAddressManager.findByValue(data.getEmailAddress()) != null)
throw new ExplicitException("Email address is already used by another user");
User user = new User();
user.setName(data.getName());
user.setFullName(data.getFullName());
user.setPassword(passwordService.encryptPassword(data.getPassword()));
userManager.save(user);
EmailAddress emailAddress = new EmailAddress();
emailAddress.setGit(true);
emailAddress.setPrimary(true);
emailAddress.setOwner(user);
emailAddress.setValue(data.getEmailAddress());
emailAddress.setVerificationCode(null);
emailAddressManager.save(emailAddress);
return user.getId();
} else {
throw new UnauthenticatedException();
}
}
private void checkEmails(User user) {
Set<String> emails = Sets.newHashSet(user.getEmail());
if (user.getGitEmail() != null)
emails.add(user.getGitEmail());
emails.addAll(user.getAlternateEmails());
for (String email: emails) {
User userWithSameEmail = userManager.findByEmail(email);
if (userWithSameEmail != null && !userWithSameEmail.equals(user))
throw new ExplicitException("Email '" + email + "' already used by another user.");
@Api(order=1950, description="Update user profile")
@Path("/{userId}")
@POST
public Long updateProfile(@PathParam("userId") Long userId, @NotNull @Valid ProfileUpdateData data) {
User user = userManager.load(userId);
if (SecurityUtils.isAdministrator() || user.equals(SecurityUtils.getUser())) {
User existingUser = userManager.findByName(data.getName());
if (existingUser != null && !existingUser.equals(user))
throw new ExplicitException("Login name is already used by another user");
user.setName(data.getName());
user.setFullName(data.getFullName());
userManager.save(user);
return user.getId();
} else {
throw new UnauthenticatedException();
}
}
}
@Api(order=2000)
@Path("/{userId}/password")
@ -322,9 +341,9 @@ public class UserResource {
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser())) {
throw new UnauthorizedException();
} else if (user.getPassword().equals(User.EXTERNAL_MANAGED)) {
if (user.getSsoInfo().getConnector() != null) {
if (user.getSsoConnector() != null) {
throw new ExplicitException("The user is currently authenticated via SSO provider '"
+ user.getSsoInfo().getConnector() + "', please change password there instead");
+ user.getSsoConnector() + "', please change password there instead");
} else {
throw new ExplicitException("The user is currently authenticated via external system, "
+ "please change password there instead");
@ -336,18 +355,6 @@ public class UserResource {
}
}
@Api(order=2100)
@Path("/{userId}/sso-info")
@POST
public Response setSsoInfo(@PathParam("userId") Long userId, @NotNull SsoInfo ssoInfo) {
if (!SecurityUtils.isAdministrator())
throw new UnauthorizedException();
User user = userManager.load(userId);
user.setSsoInfo(ssoInfo);
userManager.save(user);
return Response.ok().build();
}
@Api(order=2100)
@Path("/{userId}/queries-and-watches")
@POST
@ -398,6 +405,92 @@ public class UserResource {
userManager.delete(user);
return Response.ok().build();
}
@EntityCreate(User.class)
public static class UserCreateData implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String password;
private String fullName;
private String emailAddress;
@Api(order=100, description="Login name of the user")
@UserName
@NotEmpty
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Api(order=150)
@NotEmpty
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Api(order=200)
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
@Api(order=300)
@Email
@NotEmpty
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}
public static class ProfileUpdateData implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String fullName;
@Api(order=100, description="Login name of the user")
@UserName
@NotEmpty
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Api(order=200)
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
public static class QueriesAndWatches implements Serializable {

View File

@ -32,11 +32,9 @@ public class AuthorCriteria extends CommitCriteria {
if (value == null) { // authored by me
User user = SecurityUtils.getUser();
if (user != null) {
command.authors().add("<" + user.getEmail() + ">");
if (user.getGitEmail() != null)
command.authors().add("<" + user.getGitEmail() + ">");
for (String email: user.getAlternateEmails())
command.authors().add("<" + email + ">");
user.getEmailAddresses().stream().filter(it->it.isVerified()).forEach(it-> {
command.authors().add("<" + it.getValue() + ">");
});
} else {
throw new ExplicitException("Please login to perform this query");
}
@ -55,9 +53,8 @@ public class AuthorCriteria extends CommitCriteria {
User user = User.get();
if (user == null) {
throw new ExplicitException("Please login to perform this query");
} else if (user.getEmail().equals(authorEmail)
|| user.getGitEmail()!=null && user.getGitEmail().equals(authorEmail)
|| user.getAlternateEmails().contains(authorEmail)) {
} else if (user.getEmailAddresses().stream()
.anyMatch(it-> it.isVerified() && it.getValue().equalsIgnoreCase(authorEmail))) {
return true;
}
} else {

View File

@ -32,11 +32,9 @@ public class CommitterCriteria extends CommitCriteria {
if (value == null) { // committed by me
User user = SecurityUtils.getUser();
if (user != null) {
command.committers().add("<" + user.getEmail() + ">");
if (user.getGitEmail() != null)
command.committers().add("<" + user.getGitEmail() + ">");
for (String email: user.getAlternateEmails())
command.committers().add("<" + email + ">");
user.getEmailAddresses().stream().filter(it->it.isVerified()).forEach(it-> {
command.committers().add("<" + it.getValue() + ">");
});
} else {
throw new ExplicitException("Please login to perform this query");
}
@ -55,9 +53,8 @@ public class CommitterCriteria extends CommitCriteria {
User user = User.get();
if (user == null) {
throw new ExplicitException("Please login to perform this query");
} else if (user.getEmail().equals(committerEmail)
|| user.getGitEmail() != null && user.getGitEmail().equals(committerEmail)
|| user.getAlternateEmails().contains(committerEmail)) {
} else if (user.getEmailAddresses().stream()
.anyMatch(it-> it.isVerified() && it.getValue().equalsIgnoreCase(committerEmail))) {
return true;
}
} else {

View File

@ -16,6 +16,7 @@ import org.apache.shiro.subject.PrincipalCollection;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.request.cycle.RequestCycle;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
@ -35,6 +36,8 @@ public abstract class AbstractAuthorizingRealm extends AuthorizingRealm {
protected final UserManager userManager;
protected final EmailAddressManager emailAddressManager;
protected final GroupManager groupManager;
protected final ProjectManager projectManager;
@ -50,12 +53,13 @@ public abstract class AbstractAuthorizingRealm extends AuthorizingRealm {
@Inject
public AbstractAuthorizingRealm(UserManager userManager, GroupManager groupManager,
ProjectManager projectManager, SessionManager sessionManager,
SettingManager settingManager) {
SettingManager settingManager, EmailAddressManager emailAddressManager) {
this.userManager = userManager;
this.groupManager = groupManager;
this.projectManager = projectManager;
this.sessionManager = sessionManager;
this.settingManager = settingManager;
this.emailAddressManager = emailAddressManager;
}
private Collection<Permission> getGroupPermissions(Group group, User user) {

View File

@ -8,6 +8,7 @@ import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
@ -21,8 +22,9 @@ public class BearerAuthorizingRealm extends AbstractAuthorizingRealm {
@Inject
public BearerAuthorizingRealm(UserManager userManager, GroupManager groupManager,
ProjectManager projectManager, SessionManager sessionManager,
SettingManager settingManager) {
super(userManager, groupManager, projectManager, sessionManager, settingManager);
SettingManager settingManager, EmailAddressManager emailAddressManager) {
super(userManager, groupManager, projectManager, sessionManager,
settingManager, emailAddressManager);
setCredentialsMatcher(new AllowAllCredentialsMatcher());
}

View File

@ -20,12 +20,14 @@ import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import io.onedev.commons.utils.ExceptionUtils;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.MembershipManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.SshKeyManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.model.support.administration.authenticator.Authenticated;
import io.onedev.server.model.support.administration.authenticator.Authenticator;
@ -48,8 +50,9 @@ public class PasswordAuthorizingRealm extends AbstractAuthorizingRealm {
MembershipManager membershipManager, GroupManager groupManager,
ProjectManager projectManager, SessionManager sessionManager,
TransactionManager transactionManager, SshKeyManager sshKeyManager,
PasswordService passwordService) {
super(userManager, groupManager, projectManager, sessionManager, settingManager);
PasswordService passwordService, EmailAddressManager emailAddressManager) {
super(userManager, groupManager, projectManager, sessionManager,
settingManager, emailAddressManager);
PasswordMatcher passwordMatcher = new PasswordMatcher();
passwordMatcher.setPasswordService(passwordService);
@ -69,11 +72,19 @@ public class PasswordAuthorizingRealm extends AbstractAuthorizingRealm {
User user = new User();
user.setName(userName);
user.setPassword(User.EXTERNAL_MANAGED);
user.setEmail(authenticated.getEmail());
if (authenticated.getFullName() != null)
user.setFullName(authenticated.getFullName());
userManager.save(user);
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue(authenticated.getEmail());
emailAddress.setVerificationCode(null);
emailAddress.setOwner(user);
emailAddress.setPrimary(true);
emailAddress.setGit(true);
emailAddressManager.save(emailAddress);
user.getEmailAddresses().add(emailAddress);
Collection<String> groupNames = authenticated.getGroupNames();
if (groupNames == null && defaultGroup != null)
@ -86,14 +97,26 @@ public class PasswordAuthorizingRealm extends AbstractAuthorizingRealm {
return user;
}
private void updateUser(User user, Authenticated authenticated) {
user.setEmail(authenticated.getEmail());
private void updateUser(User user, Authenticated authenticated, @Nullable EmailAddress emailAddress) {
if (emailAddress != null) {
emailAddress.setVerificationCode(null);
emailAddressManager.setAsPrimary(emailAddress);
} else {
emailAddress = new EmailAddress();
emailAddress.setValue(authenticated.getEmail());
emailAddress.setVerificationCode(null);
emailAddress.setOwner(user);
emailAddress.setPrimary(true);
emailAddress.setGit(true);
emailAddressManager.save(emailAddress);
}
user.setSsoConnector(null);
if (authenticated.getFullName() != null)
user.setFullName(authenticated.getFullName());
userManager.save(user);
if (authenticated.getGroupNames() != null)
membershipManager.syncMemberships(user, authenticated.getGroupNames());
if (authenticated.getSshKeys() != null)
sshKeyManager.syncSshKeys(user, authenticated.getSshKeys());
}
@ -106,44 +129,54 @@ public class PasswordAuthorizingRealm extends AbstractAuthorizingRealm {
@Override
public AuthenticationInfo call() {
try {
String userName = (String) token.getPrincipal();
User user = userManager.findByName(userName);
if (user == null)
user = userManager.findByEmail(userName);
if (user == null) {
String userNameOrEmailAddressValue = (String) token.getPrincipal();
User user;
EmailAddress emailAddress = emailAddressManager.findByValue(userNameOrEmailAddressValue);
if (emailAddress != null)
user = emailAddress.getOwner();
else
user = userManager.findByName(userNameOrEmailAddressValue);
if (user != null) {
if (user.isExternalManaged()) {
Authenticator authenticator = settingManager.getAuthenticator();
if (authenticator != null) {
UsernamePasswordToken authToken = (UsernamePasswordToken) token;
authToken = new UsernamePasswordToken(user.getName(), authToken.getPassword(),
authToken.isRememberMe(), authToken.getHost());
Authenticated authenticated = authenticator.authenticate(authToken);
String emailAddressValue = authenticated.getEmail();
emailAddress = emailAddressManager.findByValue(emailAddressValue);
if (emailAddress != null && !emailAddress.getOwner().equals(user)) {
throw new AuthenticationException("Email address '" + emailAddressValue
+ "' has already been used by another user");
} else {
updateUser(user, authenticated, emailAddress);
return user;
}
} else {
throw new AuthenticationException("No external authenticator to authenticate user '"
+ userNameOrEmailAddressValue + "'");
}
} else {
return user;
}
} else if (emailAddress == null) {
Authenticator authenticator = settingManager.getAuthenticator();
if (authenticator != null) {
Authenticated authenticated = authenticator.authenticate((UsernamePasswordToken) token);
String email = authenticated.getEmail();
if (userManager.findByEmail(email) != null) {
throw new AuthenticationException("Email '" + email
+ "' has already been used by another account");
String emailAddressValue = authenticated.getEmail();
if (emailAddressManager.findByValue(emailAddressValue) != null) {
throw new AuthenticationException("Email address '" + emailAddressValue
+ "' has already been used by another user");
} else {
return newUser(userNameOrEmailAddressValue, authenticated, authenticator.getDefaultGroup());
}
user = newUser(userName, authenticated, authenticator.getDefaultGroup());
} else {
throw new UnknownAccountException("Unable to find account data for token [" + token + "] in realm [" + this + "]");
throw new UnknownAccountException("Unknown user");
}
} else if (user.getPassword().equals(User.EXTERNAL_MANAGED)) {
if (user.getSsoInfo().getConnector() != null) {
throw new AuthenticationException("Account '" + userName
+ "' is set to authenticate via " + User.AUTH_SOURCE_SSO_PROVIDER
+ user.getSsoInfo().getConnector());
}
Authenticator authenticator = settingManager.getAuthenticator();
if (authenticator != null) {
Authenticated authenticated = authenticator.authenticate((UsernamePasswordToken) token);
String email = authenticated.getEmail();
if (!email.equals(user.getEmail()) && userManager.findByEmail(email) != null) {
throw new AuthenticationException("Email '" + email
+ "' has already been used by another account");
}
updateUser(user, authenticated);
} else {
throw new AuthenticationException("Account '" + userName + "' is set to authenticate "
+ "externally but " + User.AUTH_SOURCE_EXTERNAL_AUTHENTICATOR + " is not defined");
}
}
return user;
} else {
throw new UnknownAccountException("Unknown user");
}
} catch (Exception e) {
if (e instanceof AuthenticationException) {
logger.debug("Authentication not passed", e);

View File

@ -13,14 +13,15 @@ import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import com.google.common.collect.Sets;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.MembershipManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.SshKeyManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.model.support.SsoInfo;
import io.onedev.server.model.support.administration.sso.SsoAuthenticated;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
@ -38,8 +39,8 @@ public class SsoAuthorizingRealm extends AbstractAuthorizingRealm {
public SsoAuthorizingRealm(UserManager userManager, MembershipManager membershipManager,
GroupManager groupManager, ProjectManager projectManager, SessionManager sessionManager,
TransactionManager transactionManager, SshKeyManager sshKeyManager,
SettingManager settingManager) {
super(userManager, groupManager, projectManager, sessionManager, settingManager);
SettingManager settingManager, EmailAddressManager emailAddressManager) {
super(userManager, groupManager, projectManager, sessionManager, settingManager, emailAddressManager);
setCredentialsMatcher(new AllowAllCredentialsMatcher());
this.membershipManager = membershipManager;
@ -50,14 +51,21 @@ public class SsoAuthorizingRealm extends AbstractAuthorizingRealm {
private User newUser(SsoAuthenticated authenticated) {
User user = new User();
user.setName(authenticated.getUserName());
user.setSsoConnector(authenticated.getConnector().getName());
user.setPassword(User.EXTERNAL_MANAGED);
user.setEmail(authenticated.getEmail());
if (authenticated.getFullName() != null)
user.setFullName(authenticated.getFullName());
user.setSsoInfo(authenticated.getSsoInfo());
userManager.save(user);
EmailAddress emailAddress = new EmailAddress();
emailAddress.setVerificationCode(null);
emailAddress.setValue(authenticated.getEmail());
emailAddress.setPrimary(true);
emailAddress.setGit(true);
emailAddressManager.save(emailAddress);
user.getEmailAddresses().add(emailAddress);
Collection<String> groupNames = authenticated.getGroupNames();
if (groupNames == null && authenticated.getConnector().getDefaultGroup() != null)
groupNames = Sets.newHashSet(authenticated.getConnector().getDefaultGroup());
@ -69,12 +77,18 @@ public class SsoAuthorizingRealm extends AbstractAuthorizingRealm {
return user;
}
private void updateUser(User user, SsoAuthenticated authenticated) {
private void updateUser(EmailAddress emailAddress, SsoAuthenticated authenticated) {
User user = emailAddress.getOwner();
user.setName(authenticated.getUserName());
user.setEmail(authenticated.getEmail());
user.setSsoConnector(authenticated.getConnector().getName());
user.setPassword(User.EXTERNAL_MANAGED);
if (authenticated.getFullName() != null)
user.setFullName(authenticated.getFullName());
userManager.save(user);
emailAddress.setVerificationCode(null);
emailAddressManager.setAsPrimary(emailAddress);
if (authenticated.getGroupNames() != null)
membershipManager.syncMemberships(user, authenticated.getGroupNames());
@ -94,26 +108,23 @@ public class SsoAuthorizingRealm extends AbstractAuthorizingRealm {
@Override
public AuthenticationInfo call() {
User user;
EmailAddress emailAddress;
SsoAuthenticated authenticated = (SsoAuthenticated) token;
String userName = authenticated.getUserName();
String email = authenticated.getEmail();
SsoInfo ssoInfo = authenticated.getSsoInfo();
user = userManager.findBySsoInfo(ssoInfo);
if (user == null) {
String emailAddressValue = authenticated.getEmail();
emailAddress = emailAddressManager.findByValue(emailAddressValue);
if (emailAddress == null) {
if (userManager.findByName(userName) != null)
throw new AuthenticationException("Account '" + userName + "' already exists");
if (userManager.findByEmail(email) != null)
throw new AuthenticationException("Email '" + email + "' has already been used by another account");
user = newUser(authenticated);
} else {
if (!userName.equals(user.getName()) && userManager.findByName(userName) != null)
throw new AuthenticationException("Account '" + userName + "' already exists");
if (!email.equals(user.getEmail()) && userManager.findByEmail(email) != null)
throw new AuthenticationException("Email '" + email + "' has already been used by another account");
updateUser(user, authenticated);
}
return user;
throw new AuthenticationException("Login name '" + userName + "' already used by another user");
else
return newUser(authenticated);
} else if (!userName.equalsIgnoreCase(emailAddress.getOwner().getName())
&& userManager.findByName(userName) != null) {
throw new AuthenticationException("Login name '" + userName + "' already used by another user");
} else {
updateUser(emailAddress, authenticated);
return emailAddress.getOwner();
}
}
});

View File

@ -4,7 +4,7 @@ import java.io.Serializable;
import io.onedev.commons.utils.StringUtils;
public class EmailAddress implements Serializable {
public class ParsedEmailAddress implements Serializable {
private static final long serialVersionUID = 1L;
@ -12,7 +12,7 @@ public class EmailAddress implements Serializable {
private final String domain;
public EmailAddress(String name, String domain) {
public ParsedEmailAddress(String name, String domain) {
this.name = name;
this.domain = domain;
}
@ -25,10 +25,10 @@ public class EmailAddress implements Serializable {
return domain;
}
public static EmailAddress parse(String emailAddress) {
public static ParsedEmailAddress parse(String emailAddress) {
String name = StringUtils.substringBefore(emailAddress, "@");
String domain = StringUtils.substringAfter(emailAddress, "@");
return new EmailAddress(name, domain);
return new ParsedEmailAddress(name, domain);
}
@Override

View File

@ -1,75 +0,0 @@
package io.onedev.server.util.facade;
import java.util.List;
import javax.annotation.Nullable;
import io.onedev.server.model.User;
import io.onedev.server.util.NameAndEmail;
public class UserFacade extends EntityFacade {
private static final long serialVersionUID = 1L;
private final String name;
private final String fullName;
private final String email;
private final String gitEmail;
private final List<String> alternateEmails;
public UserFacade(Long id, String name, @Nullable String fullName, String email,
@Nullable String gitEmail, List<String> alternateEmails) {
super(id);
this.name = name;
this.fullName = fullName;
this.email = email;
this.gitEmail = gitEmail;
this.alternateEmails = alternateEmails;
}
public UserFacade(User user) {
this(user.getId(), user.getName(), user.getFullName(), user.getEmail(),
user.getGitEmail(), user.getAlternateEmails());
}
public String getName() {
return name;
}
@Nullable
public String getFullName() {
return fullName;
}
public String getDisplayName() {
if (getFullName() != null)
return getFullName();
else
return getName();
}
public String getEmail() {
return email;
}
public String getGitEmail() {
return gitEmail;
}
public List<String> getAlternateEmails() {
return alternateEmails;
}
public boolean isUsingEmail(String email) {
return email.equals(this.email) || email.equals(gitEmail) || alternateEmails.contains(email);
}
public NameAndEmail getNameAndEmail() {
return new NameAndEmail(getDisplayName(), email);
}
}

View File

@ -6,9 +6,9 @@ import org.eclipse.jgit.revwalk.RevCommit;
import io.onedev.commons.loader.ExtensionPoint;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.User;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.support.administration.GroovyScript;
/**
@ -26,9 +26,10 @@ public abstract class ScriptContribution {
Build build = Build.get();
if (build != null) {
RevCommit commit = Build.get().getProject().getRevCommit(build.getCommitId(), true);
User user = OneDev.getInstance(UserManager.class).find(commit.getCommitterIdent());
if (user != null)
return user.getName();
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
EmailAddress emailAddress = emailAddressManager.findByPersonIdent(commit.getCommitterIdent());
if (emailAddress != null && emailAddress.isVerified())
return emailAddress.getOwner().getName();
else
return null;
} else {

View File

@ -37,7 +37,8 @@ public class UserNameValidator implements ConstraintValidator<UserName, String>
}
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
} else if (value.equals("new") || value.equals(User.SYSTEM_NAME) || value.equals(User.UNKNOWN_NAME)) {
} else if (value.equals("new") || value.equals(User.SYSTEM_NAME.toLowerCase())
|| value.equals(User.UNKNOWN_NAME.toLowerCase())) {
constraintContext.disableDefaultConstraintViolation();
String message = this.message;
if (message.length() == 0)
@ -54,7 +55,7 @@ public class UserNameValidator implements ConstraintValidator<UserName, String>
@Override
public String call() throws Exception {
String normalizedUserName = preferredUserName.replaceAll("[^\\w-\\.]", "-");
String normalizedUserName = preferredUserName.replaceAll("[^\\w-\\.]", "-").toLowerCase();
int suffix = 1;
UserManager userManager = OneDev.getInstance(UserManager.class);
while (true) {

View File

@ -51,6 +51,7 @@ import io.onedev.server.web.page.admin.user.accesstoken.UserAccessTokenPage;
import io.onedev.server.web.page.admin.user.authorization.UserAuthorizationsPage;
import io.onedev.server.web.page.admin.user.avatar.UserAvatarPage;
import io.onedev.server.web.page.admin.user.create.NewUserPage;
import io.onedev.server.web.page.admin.user.emailaddresses.UserEmailAddressesPage;
import io.onedev.server.web.page.admin.user.membership.UserMembershipsPage;
import io.onedev.server.web.page.admin.user.password.UserPasswordPage;
import io.onedev.server.web.page.admin.user.profile.UserProfilePage;
@ -64,6 +65,7 @@ import io.onedev.server.web.page.help.ResourceListPage;
import io.onedev.server.web.page.issues.IssueListPage;
import io.onedev.server.web.page.my.accesstoken.MyAccessTokenPage;
import io.onedev.server.web.page.my.avatar.MyAvatarPage;
import io.onedev.server.web.page.my.emailaddresses.MyEmailAddressesPage;
import io.onedev.server.web.page.my.password.MyPasswordPage;
import io.onedev.server.web.page.my.profile.MyProfilePage;
import io.onedev.server.web.page.my.sshkeys.MySshKeysPage;
@ -125,6 +127,7 @@ import io.onedev.server.web.page.project.tags.ProjectTagsPage;
import io.onedev.server.web.page.pullrequests.PullRequestListPage;
import io.onedev.server.web.page.simple.error.MethodNotAllowedErrorPage;
import io.onedev.server.web.page.simple.error.PageNotFoundErrorPage;
import io.onedev.server.web.page.simple.security.EmailAddressVerificationPage;
import io.onedev.server.web.page.simple.security.LoginPage;
import io.onedev.server.web.page.simple.security.LogoutPage;
import io.onedev.server.web.page.simple.security.PasswordResetPage;
@ -174,6 +177,7 @@ public class BaseUrlMapper extends CompoundRequestMapper {
private void addMyPages() {
add(new DynamicPathPageMapper("my/profile", MyProfilePage.class));
add(new DynamicPathPageMapper("my/email-addresses", MyEmailAddressesPage.class));
add(new DynamicPathPageMapper("my/avatar", MyAvatarPage.class));
add(new DynamicPathPageMapper("my/password", MyPasswordPage.class));
add(new DynamicPathPageMapper("my/ssh-keys", MySshKeysPage.class));
@ -213,6 +217,8 @@ public class BaseUrlMapper extends CompoundRequestMapper {
add(new DynamicPathPageMapper("logout", LogoutPage.class));
add(new DynamicPathPageMapper("signup", SignUpPage.class));
add(new DynamicPathPageMapper("reset-password", PasswordResetPage.class));
add(new DynamicPathPageMapper("verify-email-address/${emailAddress}/${verificationCode}",
EmailAddressVerificationPage.class));
add(new DynamicPathPageMapper(SsoProcessPage.MOUNT_PATH + "/${stage}/${connector}", SsoProcessPage.class));
}
@ -221,6 +227,7 @@ public class BaseUrlMapper extends CompoundRequestMapper {
add(new DynamicPathPageMapper("administration/users", UserListPage.class));
add(new DynamicPathPageMapper("administration/users/new", NewUserPage.class));
add(new DynamicPathPageMapper("administration/users/${user}/profile", UserProfilePage.class));
add(new DynamicPathPageMapper("administration/users/${user}/email-setting", UserEmailAddressesPage.class));
add(new DynamicPathPageMapper("administration/users/${user}/groups", UserMembershipsPage.class));
add(new DynamicPathPageMapper("administration/users/${user}/authorizations", UserAuthorizationsPage.class));
add(new DynamicPathPageMapper("administration/users/${user}/avatar", UserAvatarPage.class));

View File

@ -8,7 +8,6 @@ import org.eclipse.jgit.lib.PersonIdent;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.util.facade.UserFacade;
public interface AvatarManager {
@ -22,7 +21,7 @@ public interface AvatarManager {
void useAvatar(Project project, @Nullable String avatarData);
File getUploaded(UserFacade user);
File getUploaded(User user);
File getUploaded(Project project);

View File

@ -20,12 +20,12 @@ import io.onedev.commons.bootstrap.Bootstrap;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.LockUtils;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.web.component.avatarupload.AvatarUploadField;
@Singleton
@ -37,12 +37,12 @@ public class DefaultAvatarManager implements AvatarManager {
private final SettingManager settingManager;
private final UserManager userManager;
private final EmailAddressManager emailAddressManager;
@Inject
public DefaultAvatarManager(SettingManager settingManager, UserManager userManager) {
public DefaultAvatarManager(SettingManager settingManager, EmailAddressManager emailAddressManager) {
this.settingManager = settingManager;
this.userManager = userManager;
this.emailAddressManager = emailAddressManager;
}
@Sessional
@ -53,13 +53,17 @@ public class DefaultAvatarManager implements AvatarManager {
} else if (user.isSystem()) {
return AVATARS_BASE_URL + "onedev.png";
} else {
File uploadedFile = getUploaded(new UserFacade(user));
File uploadedFile = getUploaded(user);
if (uploadedFile.exists())
return AVATARS_BASE_URL + "uploaded/users/" + user.getId() + ".jpg?version=" + uploadedFile.lastModified();
if (settingManager.getSystemSetting().isGravatarEnabled())
return Gravatar.getURL(user.getEmail(), GRAVATAR_SIZE);
else
return generateAvatar(user.getName(), user.getEmail());
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress == null || !emailAddress.isVerified())
return generateAvatar(user.getName(), null);
else if (settingManager.getSystemSetting().isGravatarEnabled())
return Gravatar.getURL(emailAddress.getValue(), GRAVATAR_SIZE);
else
return generateAvatar(user.getName(), emailAddress.getValue());
}
}
@ -72,22 +76,13 @@ public class DefaultAvatarManager implements AvatarManager {
else
return AVATARS_BASE_URL + "user.png";
} else {
UserFacade user = userManager.findFacadeByEmail(personIdent.getEmailAddress());
if (user != null) {
File uploadedFile = getUploaded(user);
if (uploadedFile.exists())
return AVATARS_BASE_URL + "uploaded/users/" + user.getId() + ".jpg?version=" + uploadedFile.lastModified();
if (settingManager.getSystemSetting().isGravatarEnabled())
return Gravatar.getURL(user.getEmail(), GRAVATAR_SIZE);
else
return generateAvatar(user.getDisplayName(), user.getEmail());
} else {
if (settingManager.getSystemSetting().isGravatarEnabled())
return Gravatar.getURL(personIdent.getEmailAddress(), GRAVATAR_SIZE);
else
return generateAvatar(personIdent.getName(), personIdent.getEmailAddress());
}
EmailAddress emailAddress = emailAddressManager.findByValue(personIdent.getEmailAddress());
if (emailAddress != null && emailAddress.isVerified())
return getAvatarUrl(emailAddress.getOwner());
else if (settingManager.getSystemSetting().isGravatarEnabled())
return Gravatar.getURL(personIdent.getEmailAddress(), GRAVATAR_SIZE);
else
return generateAvatar(personIdent.getName(), personIdent.getEmailAddress());
}
}
@ -134,7 +129,7 @@ public class DefaultAvatarManager implements AvatarManager {
}
@Override
public File getUploaded(UserFacade user) {
public File getUploaded(User user) {
return new File(Bootstrap.getSiteDir(), "avatars/uploaded/users/" + user.getId() + ".jpg");
}
@ -144,7 +139,7 @@ public class DefaultAvatarManager implements AvatarManager {
Lock avatarLock = LockUtils.getLock("uploaded-user-avatar:" + user.getId());
avatarLock.lock();
try {
File avatarFile = getUploaded(new UserFacade(user));
File avatarFile = getUploaded(user);
FileUtils.createDir(avatarFile.getParentFile());
AvatarUploadField.writeToFile(avatarFile, avatarData);
} finally {

View File

@ -0,0 +1,56 @@
package io.onedev.server.web.component;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import io.onedev.server.model.EmailAddress;
@SuppressWarnings("serial")
public class EmailAddressVerificationStatusBadge extends Label {
private final IModel<EmailAddress> emailAddressModel;
public EmailAddressVerificationStatusBadge(String id, IModel<EmailAddress> emailAddressModel) {
super(id);
this.emailAddressModel = emailAddressModel;
setDefaultModel(new LoadableDetachableModel<String>() {
@Override
protected String load() {
return !emailAddressModel.getObject().isVerified()?"Unverified":"";
}
});
}
@Override
protected void onDetach() {
emailAddressModel.detach();
super.onDetach();
}
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(!emailAddressModel.getObject().isVerified());
}
@Override
protected void onInitialize() {
super.onInitialize();
add(AttributeAppender.append("class", new LoadableDetachableModel<String>() {
@Override
protected String load() {
if (!emailAddressModel.getObject().isVerified())
return "badge badge-sm badge-warning";
else
return "";
}
}));
}
}

View File

@ -807,8 +807,7 @@ public abstract class RevisionDiffPanel extends Panel {
for (User user: getProject().getAuthors(mark.getPath(),
ObjectId.fromString(mark.getCommitHash()),
new LinearRange(commentRange.getFromRow(), commentRange.getToRow()))) {
if (user.getEmail() != null)
mentions.append("@").append(user.getName()).append(" ");
mentions.append("@").append(user.getName()).append(" ");
}
}

View File

@ -221,8 +221,7 @@ public class MarkdownViewer extends GenericPanel<String> {
String avatarUrl = OneDev.getInstance(AvatarManager.class).getAvatarUrl(user);
String script = String.format("onedev.server.markdown.renderUserTooltip('%s', '%s', '%s')",
JavaScriptEscape.escapeJavaScript(avatarUrl),
JavaScriptEscape.escapeJavaScript(user.getDisplayName()),
JavaScriptEscape.escapeJavaScript(user.getEmail()));
JavaScriptEscape.escapeJavaScript(user.getDisplayName()));
target.appendJavaScript(script);
}
break;

View File

@ -996,7 +996,7 @@ onedev.server.markdown = {
$tooltip.find(".title").text(title);
$tooltip.align({placement: $tooltip.data("alignment"), target: {element: $tooltip.data("trigger")}});
},
renderUserTooltip: function(avatarUrl, name, email) {
renderUserTooltip: function(avatarUrl, name) {
var $tooltip = $("#reference-tooltip");
$tooltip.empty().append("" +
"<div class='d-flex align-items-center'>" +

View File

@ -21,7 +21,7 @@ import io.onedev.server.model.Project;
import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.search.entity.project.ProjectQueryLexer;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.EmailAddress;
import io.onedev.server.util.ParsedEmailAddress;
import io.onedev.server.util.criteria.Criteria;
import io.onedev.server.web.component.markdown.MarkdownViewer;
import io.onedev.server.web.component.modal.ModalLink;
@ -101,7 +101,7 @@ public abstract class ProjectInfoPanel extends Panel {
String subAddressed;
EmailAddress emailAddress = EmailAddress.parse(settingManager.getMailSetting().getEmailAddress());
ParsedEmailAddress emailAddress = ParsedEmailAddress.parse(settingManager.getMailSetting().getEmailAddress());
if (getProject().getServiceDeskName() != null)
subAddressed = emailAddress.getSubAddressed(getProject().getServiceDeskName());
else

View File

@ -2,7 +2,7 @@
display: none;
}
.project-list .row-selector {
padding-top: 1rem;
padding-top: 16px;
}
.project-list .project-summary {
margin-left: 2px;

View File

@ -191,7 +191,7 @@
*/
.select2-container .select2-choice .select2-arrow b,
.select2-container .select2-choice div b {
background-position: 0 6px;
background-position: 0 8px;
}
.select2-dropdown-open .select2-choice .select2-arrow b,
@ -203,28 +203,28 @@
.input-group-sm .select2-container .select2-choice .select2-arrow b,
.select2-container.form-control-sm .select2-choice div b,
.input-group-sm .select2-container .select2-choice div b {
background-position: 0 1px;
background-position: 0 2px;
}
.select2-dropdown-open.form-control-sm .select2-choice .select2-arrow b,
.input-group-sm .select2-dropdown-open .select2-choice .select2-arrow b,
.select2-dropdown-open.form-control-sm .select2-choice div b,
.input-group-sm .select2-dropdown-open .select2-choice div b {
background-position: -18px 1px;
background-position: -18px 2px;
}
.select2-container.form-control-lg .select2-choice .select2-arrow b,
.input-group-lg .select2-container .select2-choice .select2-arrow b,
.select2-container.form-control-lg .select2-choice div b,
.input-group-lg .select2-container .select2-choice div b {
background-position: 0 9px;
background-position: 0 10px;
}
.select2-dropdown-open.form-control-lg .select2-choice .select2-arrow b,
.input-group-lg .select2-dropdown-open .select2-choice .select2-arrow b,
.select2-dropdown-open.form-control-lg .select2-choice div b,
.input-group-lg .select2-dropdown-open .select2-choice div b {
background-position: -18px 9px;
background-position: -18px 10px;
}
/**

View File

@ -12,7 +12,6 @@ import org.apache.wicket.model.PropertyModel;
import io.onedev.server.OneDev;
import io.onedev.server.model.User;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.web.avatar.AvatarManager;
import io.onedev.server.web.component.avatarupload.AvatarFileSelected;
import io.onedev.server.web.component.avatarupload.AvatarUploadField;
@ -46,7 +45,7 @@ public class AvatarEditPanel extends GenericPanel<User> {
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(getAvatarManager().getUploaded(new UserFacade(getUser())).exists());
setVisible(getAvatarManager().getUploaded(getUser()).exists());
}
@Override

View File

@ -10,9 +10,9 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.unbescape.html.HtmlEscape;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.web.component.user.UserAvatar;
@SuppressWarnings("serial")
@ -39,12 +39,14 @@ public class PersonCardPanel extends Panel {
StringBuilder builder = new StringBuilder();
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
String displayName;
UserFacade user = OneDev.getInstance(UserManager.class).findFacadeByEmail(personIdent.getEmailAddress());
if (user != null)
displayName = user.getDisplayName();
EmailAddress emailAddress = emailAddressManager.findByValue(personIdent.getEmailAddress());
if (emailAddress != null && emailAddress.isVerified())
displayName = emailAddress.getOwner().getDisplayName();
else
displayName = personIdent.getName();
builder.append("<div>" + HtmlEscape.escapeHtml5(displayName) + " <i>(" + gitRole + ")</i></div>");
if (StringUtils.isBlank(personIdent.getEmailAddress())) {
@ -53,8 +55,8 @@ public class PersonCardPanel extends Panel {
else
builder.append("<i>No OneDev Account</i>");
} else {
if (user != null)
builder.append("<i>@" + HtmlEscape.escapeHtml5(user.getName()) + "</i>");
if (emailAddress != null && emailAddress.isVerified())
builder.append("<i>@" + HtmlEscape.escapeHtml5(emailAddress.getOwner().getName()) + "</i>");
else
builder.append("<i>No OneDev Account</i>");
}

View File

@ -0,0 +1,36 @@
<wicket:panel>
<div class="email-addresses">
<ul class="alert alert-light px-5 mb-5">
<li>Primary email address will be used to receive notifications, show gravatar (if enabled) etc.</li>
<li>Git email address will be used as git author/committer for commits created on web UI</li>
<li>When determine if <span wicket:id="who"></span> author/committer of a git commit, all emails listed here will be checked</li>
<li>Unverified email address is <b>NOT</b> applicable for above functionalities</li>
</ul>
<table class="table">
<tr wicket:id="emailAddresses">
<td>
<span wicket:id="value" class="font-size-lg"></span>
<span wicket:id="primary" class="badge badge-info badge-sm ml-3">Primary</span>
<span wicket:id="git" class="badge badge-info badge-sm ml-1">Git</span>
<span wicket:id="verificationStatus" class="ml-1"></span>
<div wicket:id="externalManagedNote" class="text-warning font-size-sm mt-2"></div>
</td>
<td>
<a wicket:id="operations" title="Operatioins"><wicket:svg href="ellipsis" class="icon"/></a>
</td>
</tr>
<tr>
<td colspan="2">
<div class="mb-2 mt-4 font-weight-bolder">Add New Email Address</div>
<div class="form-group">
<form wicket:id="form" class="form-inline">
<input wicket:id="emailAddress" class="form-control form-control-sm flex-flow-0 mr-3" placeholder="Email Address">
<button type="submit" class="btn btn-icon btn-sm btn-primary"><wicket:svg href="plus" class="icon"/></button>
</form>
<div wicket:id="feedback"></div>
</div>
</td>
</tr>
</table>
</div>
</wicket:panel>

View File

@ -0,0 +1,254 @@
package io.onedev.server.web.component.user.emailaddresses;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Session;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.feedback.FencedFeedbackPanel;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.validation.validator.EmailAddressValidator;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.component.EmailAddressVerificationStatusBadge;
import io.onedev.server.web.component.floating.FloatingPanel;
import io.onedev.server.web.component.menu.MenuItem;
import io.onedev.server.web.component.menu.MenuLink;
import io.onedev.server.web.page.my.MyPage;
import io.onedev.server.web.util.ConfirmClickModifier;
@SuppressWarnings("serial")
public class EmailAddressesPanel extends GenericPanel<User> {
private String emailAddressValue;
public EmailAddressesPanel(String id, IModel<User> model) {
super(id, model);
}
@Override
protected void onInitialize() {
super.onInitialize();
if (getPage() instanceof MyPage)
add(new Label("who", "you are "));
else
add(new Label("who", "this user is "));
add(new ListView<EmailAddress>("emailAddresses", new AbstractReadOnlyModel<List<EmailAddress>>() {
@Override
public List<EmailAddress> getObject() {
return getUser().getSortedEmailAddresses();
}
}) {
@Override
protected void populateItem(ListItem<EmailAddress> item) {
EmailAddress address = item.getModelObject();
item.add(new Label("value", address.getValue()));
item.add(new WebMarkupContainer("primary")
.setVisible(address.equals(getUser().getPrimaryEmailAddress())));
item.add(new WebMarkupContainer("git")
.setVisible(address.equals(getUser().getGitEmailAddress())));
item.add(new EmailAddressVerificationStatusBadge("verificationStatus", item.getModel()));
if (getUser().isExternalManaged() && address.equals(getUser().getPrimaryEmailAddress())) {
item.add(new Label("externalManagedNote",
"This primary email address is managed from " + getUser().getAuthSource()));
} else {
item.add(new WebMarkupContainer("externalManagedNote").setVisible(false));
}
item.add(new MenuLink("operations") {
@Override
protected List<MenuItem> getMenuItems(FloatingPanel dropdown) {
List<MenuItem> menuItems = new ArrayList<>();
EmailAddress address = item.getModelObject();
Long addressId = address.getId();
if (!getUser().isExternalManaged() && !address.equals(getUser().getPrimaryEmailAddress())) {
menuItems.add(new MenuItem() {
@Override
public String getLabel() {
return "Set As Primary";
}
@Override
public WebMarkupContainer newLink(String id) {
return new Link<Void>(id) {
@Override
public void onClick() {
getEmailAddressManager().setAsPrimary(getEmailAddressManager().load(addressId));
}
};
}
});
}
if (!address.equals(getUser().getGitEmailAddress())) {
menuItems.add(new MenuItem() {
@Override
public String getLabel() {
return "Use For Git Operations";
}
@Override
public WebMarkupContainer newLink(String id) {
return new Link<Void>(id) {
@Override
public void onClick() {
getEmailAddressManager().useForGitOperations(getEmailAddressManager().load(addressId));
}
};
}
});
}
if (!address.isVerified()) {
menuItems.add(new MenuItem() {
@Override
public String getLabel() {
return "Resend Verification Email";
}
@Override
public WebMarkupContainer newLink(String id) {
return new AjaxLink<Void>(id) {
@Override
public void onClick(AjaxRequestTarget target) {
if (OneDev.getInstance(SettingManager.class).getMailSetting() != null) {
getEmailAddressManager().sendVerificationEmail(item.getModelObject());
Session.get().success("Verification email sent, please check it");
} else {
target.appendJavaScript(String.format("alert('%s');",
"Unable to send verification email as system mail setting is not defined yet"));
}
dropdown.close();
}
};
}
});
}
if (!(getUser().isExternalManaged() && address.equals(getUser().getPrimaryEmailAddress()))
&& getUser().getEmailAddresses().size() > 1) {
menuItems.add(new MenuItem() {
@Override
public String getLabel() {
return "Delete";
}
@Override
public WebMarkupContainer newLink(String id) {
Link<Void> link = new Link<Void>(id) {
@Override
public void onClick() {
getEmailAddressManager().delete(getEmailAddressManager().load(addressId));
}
};
link.add(new ConfirmClickModifier("Do you really want to delete this email address?"));
return link;
}
});
}
return menuItems;
}
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(!getMenuItems(null).isEmpty());
}
});
}
});
Form<?> form = new Form<Void>("form") {
@Override
protected void onSubmit() {
super.onSubmit();
if (getEmailAddressManager().findByValue(emailAddressValue) != null) {
error("This email address is being used");
} else {
EmailAddress address = new EmailAddress();
address.setValue(emailAddressValue);
address.setOwner(getUser());
if (SecurityUtils.isAdministrator())
address.setVerificationCode(null);
getEmailAddressManager().save(address);
emailAddressValue = null;
}
}
};
TextField<String> input = new TextField<String>("emailAddress", new IModel<String>() {
@Override
public void detach() {
}
@Override
public String getObject() {
return emailAddressValue;
}
@Override
public void setObject(String object) {
emailAddressValue = object;
}
});
input.setLabel(Model.of("Email address"));
input.setRequired(true);
input.add(EmailAddressValidator.getInstance());
form.add(input);
add(new FencedFeedbackPanel("feedback", form));
add(form);
}
private EmailAddressManager getEmailAddressManager() {
return OneDev.getInstance(EmailAddressManager.class);
}
private User getUser() {
return getModelObject();
}
}

View File

@ -10,8 +10,8 @@ import org.apache.wicket.markup.html.panel.Panel;
import org.eclipse.jgit.lib.PersonIdent;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.web.behavior.dropdown.DropdownHoverBehavior;
import io.onedev.server.web.component.floating.AlignPlacement;
import io.onedev.server.web.component.user.UserAvatar;
@ -39,9 +39,10 @@ public class PersonIdentPanel extends Panel {
add(new UserAvatar("avatar", personIdent).setVisible(mode != Mode.NAME));
UserFacade user = OneDev.getInstance(UserManager.class).findFacadeByEmail(personIdent.getEmailAddress());
if (user != null)
add(new Label("name", user.getDisplayName()).setVisible(mode != Mode.AVATAR));
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
EmailAddress emailAddress = emailAddressManager.findByValue(personIdent.getEmailAddress());
if (emailAddress != null && emailAddress.isVerified())
add(new Label("name", emailAddress.getOwner().getDisplayName()).setVisible(mode != Mode.AVATAR));
else
add(new Label("name", personIdent.getName()).setVisible(mode != Mode.AVATAR));

View File

@ -65,9 +65,6 @@ public class ProfileEditPanel extends GenericPanel<User> {
super.onSubmit();
User user = getUser();
user.getAlternateEmails().remove(user.getEmail());
if (user.getGitEmail() != null)
user.getAlternateEmails().remove(user.getGitEmail());
UserManager userManager = OneDev.getInstance(UserManager.class);
User userWithSameName = userManager.findByName(user.getName());
@ -75,25 +72,6 @@ public class ProfileEditPanel extends GenericPanel<User> {
editor.error(new Path(new PathNode.Named(User.PROP_NAME)),
"Login name already used by another account");
}
User userWithSameEmail = userManager.findByEmail(user.getEmail());
if (userWithSameEmail != null && !userWithSameEmail.equals(user)) {
editor.error(new Path(new PathNode.Named(User.PROP_EMAIL)),
"Email already used by another account");
}
if (user.getGitEmail() != null) {
userWithSameEmail = userManager.findByEmail(user.getGitEmail());
if (userWithSameEmail != null && !userWithSameEmail.equals(user)) {
editor.error(new Path(new PathNode.Named(User.PROP_GIT_EMAIL)),
"Email already used by another account");
}
}
for (String email: user.getAlternateEmails()) {
userWithSameEmail = userManager.findByEmail(email);
if (userWithSameEmail != null && !userWithSameEmail.equals(user)) {
editor.error(new Path(new PathNode.Named(User.PROP_ALTERNATE_EMAILS)),
"Email '" + email + "' already used by another account");
}
}
if (editor.isValid()) {
userManager.save(user, oldName);

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.twofactorauthentication;
package io.onedev.server.web.component.user.twofactorauthentication;
import java.io.IOException;
import java.security.SecureRandom;

View File

@ -23,6 +23,9 @@ import org.apache.wicket.model.AbstractReadOnlyModel;
import com.google.common.base.Preconditions;
import io.onedev.server.OneDev;
import io.onedev.server.persistence.TransactionManager;
@SuppressWarnings("serial")
public abstract class Wizard extends Panel {
@ -116,7 +119,14 @@ public abstract class Wizard extends Panel {
@Override
public void onSubmit() {
super.onSubmit();
getActiveStep().complete();
OneDev.getInstance(TransactionManager.class).run(new Runnable() {
@Override
public void run() {
getActiveStep().complete();
}
});
activeStepIndex++;
form.replace(getActiveStep().render("content"));
}
@ -133,7 +143,14 @@ public abstract class Wizard extends Panel {
@Override
public void onSubmit() {
super.onSubmit();
getActiveStep().complete();
OneDev.getInstance(TransactionManager.class).run(new Runnable() {
@Override
public void run() {
getActiveStep().complete();
}
});
finished();
}

View File

@ -9,6 +9,7 @@ import java.util.stream.Collectors;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.convert.ConversionException;
@ -50,7 +51,7 @@ public class UserMultiChoiceEditor extends PropertyEditor<List<String>> {
choices.addAll((List<User>)ReflectionUtils
.invokeStaticMethod(descriptor.getBeanClass(), userChoice.value()));
} else {
choices.addAll(OneDev.getInstance(UserManager.class).query());
choices.addAll(getUserManager().query());
choices.sort(Comparator.comparing(User::getDisplayName));
}
} finally {
@ -59,15 +60,42 @@ public class UserMultiChoiceEditor extends PropertyEditor<List<String>> {
List<User> selections = new ArrayList<>();
if (getModelObject() != null) {
UserManager userManager = OneDev.getInstance(UserManager.class);
for (String userName: getModelObject()) {
User user = userManager.findByName(userName);
User user = getUserManager().findByName(userName);
if (user != null && choices.contains(user))
selections.add(user);
}
}
input = new UserMultiChoice("input", Model.of(selections), Model.of(choices)) {
List<Long> choiceIds = choices.stream().map(it->it.getId()).collect(Collectors.toList());
List<Long> selectionIds = selections.stream().map(it->it.getId()).collect(Collectors.toList());
input = new UserMultiChoice("input", new IModel<Collection<User>>() {
@Override
public void detach() {
}
@Override
public Collection<User> getObject() {
return selectionIds.stream().map(it-> getUserManager().load(it)).collect(Collectors.toList());
}
@Override
public void setObject(Collection<User> object) {
selectionIds.clear();
if (object != null)
selectionIds.addAll(object.stream().map(it->it.getId()).collect(Collectors.toList()));
}
}, new LoadableDetachableModel<Collection<User>>() {
@Override
protected Collection<User> load() {
return choiceIds.stream().map(it-> getUserManager().load(it)).collect(Collectors.toList());
}
}) {
@Override
protected void onInitialize() {
@ -90,6 +118,10 @@ public class UserMultiChoiceEditor extends PropertyEditor<List<String>> {
add(input);
}
private UserManager getUserManager() {
return OneDev.getInstance(UserManager.class);
}
@Override
protected List<String> convertInputToValue() throws ConversionException {

View File

@ -1,12 +1,16 @@
package io.onedev.server.web.editable.userchoice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.convert.ConversionException;
@ -32,6 +36,10 @@ public class UserSingleChoiceEditor extends PropertyEditor<String> {
super(id, propertyDescriptor, propertyModel);
}
private UserManager getUserManager() {
return OneDev.getInstance(UserManager.class);
}
@SuppressWarnings("unchecked")
@Override
protected void onInitialize() {
@ -48,7 +56,7 @@ public class UserSingleChoiceEditor extends PropertyEditor<String> {
choices.addAll((List<User>)ReflectionUtils
.invokeStaticMethod(descriptor.getBeanClass(), userChoice.value()));
} else {
choices.addAll(OneDev.getInstance(UserManager.class).query());
choices.addAll(getUserManager().query());
choices.sort(Comparator.comparing(User::getDisplayName));
}
} finally {
@ -57,14 +65,43 @@ public class UserSingleChoiceEditor extends PropertyEditor<String> {
User selection;
if (getModelObject() != null)
selection = OneDev.getInstance(UserManager.class).findByName(getModelObject());
selection = getUserManager().findByName(getModelObject());
else
selection = null;
if (selection != null && !choices.contains(selection))
selection = null;
input = new UserSingleChoice("input", Model.of(selection), Model.of(choices)) {
List<Long> choiceIds = choices.stream().map(it->it.getId()).collect(Collectors.toList());
AtomicReference<Long> selectionId = new AtomicReference<>(null);
if (selection != null)
selectionId.set(selection.getId());
input = new UserSingleChoice("input", new IModel<User>() {
@Override
public void detach() {
}
@Override
public User getObject() {
return selectionId.get()!=null? getUserManager().load(selectionId.get()): null;
}
@Override
public void setObject(User object) {
selectionId.set(User.idOf(object));
}
}, new LoadableDetachableModel<Collection<User>>() {
@Override
protected Collection<User> load() {
return choiceIds.stream().map(it-> getUserManager().load(it)).collect(Collectors.toList());
}
}) {
@Override
protected void onInitialize() {

View File

@ -8,7 +8,7 @@
<input wicket:id="addNew" type="hidden" class="form-control">
</div>
<div class="mb-4">
<a wicket:id="delete" class="btn btn-light-danger">Delete</a>
<a wicket:id="delete" class="btn btn-light-danger">Delete <wicket:svg href="arrow" class="rotate-90 icon"/></a>
</div>
</div>
<table wicket:id="memberships" class="memberships table"></table>
@ -16,4 +16,8 @@
<wicket:fragment wicket:id="nameFrag">
<a wicket:id="link"><img wicket:id="avatar"> <span wicket:id="name" class="name"></span></a>
</wicket:fragment>
<wicket:fragment wicket:id="emailFrag">
<span wicket:id="emailAddress"></span>
<span wicket:id="verificationStatus" class="ml-1"></span>
</wicket:fragment>
</wicket:extend>

View File

@ -36,6 +36,7 @@ import org.hibernate.criterion.Restrictions;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.MembershipManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Membership;
import io.onedev.server.model.User;
import io.onedev.server.persistence.dao.EntityCriteria;
@ -44,6 +45,7 @@ import io.onedev.server.util.match.MatchScoreProvider;
import io.onedev.server.util.match.MatchScoreUtils;
import io.onedev.server.web.WebConstants;
import io.onedev.server.web.behavior.OnTypingDoneBehavior;
import io.onedev.server.web.component.EmailAddressVerificationStatusBadge;
import io.onedev.server.web.component.datatable.OneDataTable;
import io.onedev.server.web.component.datatable.selectioncolumn.SelectionColumn;
import io.onedev.server.web.component.floating.FloatingPanel;
@ -324,13 +326,23 @@ public class GroupMembershipsPage extends GroupPage {
}
});
columns.add(new AbstractColumn<Membership, Void>(Model.of("Email")) {
columns.add(new AbstractColumn<Membership, Void>(Model.of("Primary Email")) {
@Override
public void populateItem(Item<ICellPopulator<Membership>> cellItem, String componentId,
IModel<Membership> rowModel) {
cellItem.add(new Label(componentId, rowModel.getObject().getUser().getEmail()));
EmailAddress emailAddress = rowModel.getObject().getUser().getPrimaryEmailAddress();
if (emailAddress != null) {
Fragment fragment = new Fragment(componentId, "emailFrag", GroupMembershipsPage.this);
fragment.add(new Label("emailAddress", emailAddress.getValue()));
fragment.add(new EmailAddressVerificationStatusBadge(
"verificationStatus", Model.of(emailAddress)));
cellItem.add(fragment);
} else {
cellItem.add(new Label(componentId, "<i>Not specified</i>").setEscapeModelStrings(false));
}
}
});
dataProvider = new SortableDataProvider<Membership, Void>() {

View File

@ -1,6 +1,7 @@
package io.onedev.server.web.page.admin.mailsetting;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@ -22,17 +23,18 @@ import org.apache.wicket.util.visit.IVisitor;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.User;
import io.onedev.server.model.support.administration.MailSetting;
import io.onedev.server.model.support.administration.ReceiveMailSetting;
import io.onedev.server.notification.MailManager;
import io.onedev.server.notification.MailPosition;
import io.onedev.server.notification.MessageListener;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.EmailAddress;
import io.onedev.server.util.ParsedEmailAddress;
import io.onedev.server.web.component.taskbutton.TaskButton;
import io.onedev.server.web.editable.BeanContext;
import io.onedev.server.web.editable.BeanEditor;
@ -66,6 +68,7 @@ public class MailSettingPage extends AdministrationPage {
}
};
TaskButton testButton = new TaskButton("test") {
@Override
@ -103,54 +106,64 @@ public class MailSettingPage extends AdministrationPage {
@Override
protected String runTask(TaskLogger logger) {
User user = SecurityUtils.getUser();
MailManager mailManager = OneDev.getInstance(MailManager.class);
MailSetting mailSetting = mailSettingHolder.getMailSetting();
if (mailSetting.getReceiveMailSetting() != null) {
String uuid = UUID.randomUUID().toString();
AtomicReference<Future<?>> futureRef = new AtomicReference<>(null);
MessageListener listener = new MessageListener() {
@Override
public void onReceived(Message message) throws MessagingException {
if (message.getSubject().contains(uuid))
return OneDev.getInstance(SessionManager.class).call(new Callable<String>() {
@Override
public String call() {
MailManager mailManager = OneDev.getInstance(MailManager.class);
MailSetting mailSetting = mailSettingHolder.getMailSetting();
if (mailSetting.getReceiveMailSetting() != null) {
String uuid = UUID.randomUUID().toString();
AtomicReference<Future<?>> futureRef = new AtomicReference<>(null);
MessageListener listener = new MessageListener() {
@Override
public void onReceived(Message message) throws MessagingException {
if (message.getSubject().contains(uuid))
futureRef.get().cancel(true);
}
};
futureRef.set(mailManager.monitorInbox(mailSetting.getReceiveMailSetting(),
mailSetting.getTimeout(), listener, new MailPosition()));
ParsedEmailAddress emailAddress = ParsedEmailAddress.parse(mailSetting.getEmailAddress());
String subAddressed = emailAddress.getSubAddressed(MailManager.TEST_SUB_ADDRESS);
logger.log("Sending test mail to " + subAddressed + "...");
mailManager.sendMail(mailSetting,
Sets.newHashSet(subAddressed), Lists.newArrayList(), Lists.newArrayList(), uuid,
"[Test] Test Email From OneDev", "This is a test email from OneDev", null, null);
logger.log("Waiting for test mail to come back...");
try {
futureRef.get().get();
} catch (CancellationException e) {
} catch (InterruptedException e) {
futureRef.get().cancel(true);
}
};
futureRef.set(mailManager.monitorInbox(mailSetting.getReceiveMailSetting(),
mailSetting.getTimeout(), listener, new MailPosition()));
EmailAddress emailAddress = EmailAddress.parse(mailSetting.getEmailAddress());
String subAddressed = emailAddress.getSubAddressed(MailManager.TEST_SUB_ADDRESS);
logger.log("Sending test mail to " + subAddressed + "...");
mailManager.sendMail(mailSetting,
Sets.newHashSet(subAddressed), Lists.newArrayList(), Lists.newArrayList(), uuid,
"[Test] Test Email From OneDev", "This is a test email from OneDev", null, null);
logger.log("Waiting for test mail to come back...");
try {
futureRef.get().get();
} catch (CancellationException e) {
} catch (InterruptedException e) {
futureRef.get().cancel(true);
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
logger.log("Received test mail");
return "Great, your mail setting is working";
} else {
io.onedev.server.model.EmailAddress emailAddress = SecurityUtils.getUser().getPrimaryEmailAddress();
if (emailAddress != null) {
String body = "Great, your mail setting is working!";
mailManager.sendMail(mailSettingHolder.getMailSetting(), Sets.newHashSet(emailAddress.getValue()),
Lists.newArrayList(), Lists.newArrayList(), "[Test] Test Email From OneDev",
body, body, null, null);
return "Test mail has been sent to " + emailAddress.getValue() + ", please check your mail box";
} else {
throw new ExplicitException("Primary email address of your account is not specified yet");
}
}
}
logger.log("Received test mail");
return "Great, your mail setting is working";
} else {
String body = "Great, your mail setting is working!";
mailManager.sendMail(mailSettingHolder.getMailSetting(), Sets.newHashSet(user.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), "[Test] Test Email From OneDev",
body, body, null, null);
return "Test mail has been sent to " + user.getEmail() + ", please check your mail box";
}
});
}
};

View File

@ -17,4 +17,8 @@
<a wicket:id="impersonate" class="btn btn-xs btn-icon btn-light btn-hover-primary mr-1" title="Impersonate this user"><wicket:svg href="user-tick" class="icon align-middle"/></a>
<a wicket:id="delete" class="btn btn-xs btn-icon btn-light btn-hover-danger" title="Delete this user"><wicket:svg href="trash" class="icon align-middle"></wicket:svg></a>
</wicket:fragment>
<wicket:fragment wicket:id="emailFrag">
<span wicket:id="emailAddress"></span>
<span wicket:id="verificationStatus" class="ml-1"></span>
</wicket:fragment>
</wicket:extend>

View File

@ -29,19 +29,17 @@ import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.WebConstants;
import io.onedev.server.web.WebSession;
import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
import io.onedev.server.web.behavior.OnTypingDoneBehavior;
import io.onedev.server.web.component.EmailAddressVerificationStatusBadge;
import io.onedev.server.web.component.datatable.OneDataTable;
import io.onedev.server.web.component.link.ActionablePageLink;
import io.onedev.server.web.component.user.UserAvatar;
@ -73,19 +71,6 @@ public class UserListPage extends AdministrationPage {
query = params.get(PARAM_QUERY).toString();
}
private EntityCriteria<User> getCriteria() {
EntityCriteria<User> criteria = EntityCriteria.of(User.class);
criteria.add(Restrictions.gt("id", 0L));
if (query != null) {
criteria.add(Restrictions.or(
Restrictions.ilike("name", query, MatchMode.ANYWHERE),
Restrictions.ilike("fullName", query, MatchMode.ANYWHERE)));
} else {
criteria.setCacheable(true);
}
return criteria;
}
@Override
protected void onPopState(AjaxRequestTarget target, Serializable data) {
super.onPopState(target, data);
@ -203,7 +188,7 @@ public class UserListPage extends AdministrationPage {
});
columns.add(new AbstractColumn<User, Void>(Model.of("Email")) {
columns.add(new AbstractColumn<User, Void>(Model.of("Primary Email")) {
@Override
public String getCssClass() {
@ -213,7 +198,16 @@ public class UserListPage extends AdministrationPage {
@Override
public void populateItem(Item<ICellPopulator<User>> cellItem, String componentId,
IModel<User> rowModel) {
cellItem.add(new Label(componentId, rowModel.getObject().getEmail()));
EmailAddress emailAddress = rowModel.getObject().getPrimaryEmailAddress();
if (emailAddress != null) {
Fragment fragment = new Fragment(componentId, "emailFrag", UserListPage.this);
fragment.add(new Label("emailAddress", emailAddress.getValue()));
fragment.add(new EmailAddressVerificationStatusBadge(
"verificationStatus", Model.of(emailAddress)));
cellItem.add(fragment);
} else {
cellItem.add(new Label(componentId, "<i>Not specified</i>").setEscapeModelStrings(false));
}
}
});
@ -303,14 +297,12 @@ public class UserListPage extends AdministrationPage {
@Override
public Iterator<? extends User> iterator(long first, long count) {
EntityCriteria<User> criteria = getCriteria();
criteria.addOrder(Order.asc("name"));
return OneDev.getInstance(UserManager.class).query(criteria, (int)first, (int)count).iterator();
return getUserManager().query(query, (int)first, (int)count).iterator();
}
@Override
public long calcSize() {
return OneDev.getInstance(UserManager.class).count(getCriteria());
return OneDev.getInstance(UserManager.class).count(query);
}
@Override
@ -348,6 +340,10 @@ public class UserListPage extends AdministrationPage {
add(usersTable = new OneDataTable<User, Void>("users", columns, dataProvider,
WebConstants.PAGE_SIZE, pagingHistorySupport));
}
private UserManager getUserManager() {
return OneDev.getInstance(UserManager.class);
}
@Override
public void renderHead(IHeaderResponse response) {

View File

@ -26,6 +26,7 @@ import io.onedev.server.web.page.admin.AdministrationPage;
import io.onedev.server.web.page.admin.user.accesstoken.UserAccessTokenPage;
import io.onedev.server.web.page.admin.user.authorization.UserAuthorizationsPage;
import io.onedev.server.web.page.admin.user.avatar.UserAvatarPage;
import io.onedev.server.web.page.admin.user.emailaddresses.UserEmailAddressesPage;
import io.onedev.server.web.page.admin.user.membership.UserMembershipsPage;
import io.onedev.server.web.page.admin.user.password.UserPasswordPage;
import io.onedev.server.web.page.admin.user.profile.UserProfilePage;
@ -66,6 +67,7 @@ public abstract class UserPage extends AdministrationPage {
List<PageTab> tabs = new ArrayList<>();
tabs.add(new UserTab("Profile", "profile", UserProfilePage.class));
tabs.add(new UserTab("Email Addresses", "mail", UserEmailAddressesPage.class));
tabs.add(new UserTab("Edit Avatar", "avatar", UserAvatarPage.class));
tabs.add(new UserTab("Change Password", "password", UserPasswordPage.class));

View File

@ -11,12 +11,13 @@ import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import com.google.common.collect.Sets;
import io.onedev.commons.loader.AppLoader;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.util.Path;
import io.onedev.server.util.PathNode;
import io.onedev.server.web.editable.BeanContext;
@ -24,11 +25,12 @@ import io.onedev.server.web.editable.BeanEditor;
import io.onedev.server.web.page.admin.AdministrationPage;
import io.onedev.server.web.page.admin.user.UserCssResourceReference;
import io.onedev.server.web.page.admin.user.membership.UserMembershipsPage;
import io.onedev.server.web.util.NewUserBean;
@SuppressWarnings("serial")
public class NewUserPage extends AdministrationPage {
private User user = new User();
private NewUserBean newUserBean = new NewUserBean();
private boolean continueToAdd;
@ -40,8 +42,7 @@ public class NewUserPage extends AdministrationPage {
protected void onInitialize() {
super.onInitialize();
BeanEditor editor = BeanContext.edit("editor", user,
Sets.newHashSet(User.PROP_GIT_EMAIL, User.PROP_ALTERNATE_EMAILS), true);
BeanEditor editor = BeanContext.edit("editor", newUserBean);
Form<?> form = new Form<Void>("form") {
@ -50,25 +51,43 @@ public class NewUserPage extends AdministrationPage {
super.onSubmit();
UserManager userManager = OneDev.getInstance(UserManager.class);
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
User userWithSameName = userManager.findByName(user.getName());
User userWithSameName = userManager.findByName(newUserBean.getName());
if (userWithSameName != null) {
editor.error(new Path(new PathNode.Named(User.PROP_NAME)),
"Login name already used by another account");
}
User userWithSameEmail = userManager.findByEmail(user.getEmail());
if (userWithSameEmail != null) {
editor.error(new Path(new PathNode.Named(User.PROP_EMAIL)),
"Email already used by another account");
if (emailAddressManager.findByValue(newUserBean.getEmailAddress()) != null) {
editor.error(new Path(new PathNode.Named(NewUserBean.PROP_EMAIL_ADDRESS)),
"Email address already used by another user");
}
if (editor.isValid()){
user.setPassword(AppLoader.getInstance(PasswordService.class).encryptPassword(user.getPassword()));
userManager.save(user, null);
User user = new User();
user.setName(newUserBean.getName());
user.setFullName(newUserBean.getFullName());
user.setPassword(AppLoader.getInstance(PasswordService.class).encryptPassword(newUserBean.getPassword()));
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue(newUserBean.getEmailAddress());
emailAddress.setOwner(user);
emailAddress.setVerificationCode(null);
OneDev.getInstance(TransactionManager.class).run(new Runnable() {
@Override
public void run() {
userManager.save(user);
emailAddressManager.save(emailAddress);
}
});
Session.get().success("New user created");
if (continueToAdd) {
user = new User();
replace(BeanContext.edit("editor", user));
newUserBean = new NewUserBean();
replace(BeanContext.edit("editor", newUserBean));
} else {
setResponsePage(UserMembershipsPage.class, UserMembershipsPage.paramsOf(user));
}

View File

@ -0,0 +1,3 @@
<wicket:extend>
<div wicket:id="content"></div>
</wicket:extend>

View File

@ -0,0 +1,30 @@
package io.onedev.server.web.page.admin.user.emailaddresses;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import io.onedev.server.model.User;
import io.onedev.server.web.component.user.emailaddresses.EmailAddressesPanel;
import io.onedev.server.web.page.admin.user.UserPage;
@SuppressWarnings("serial")
public class UserEmailAddressesPage extends UserPage {
public UserEmailAddressesPage(PageParameters params) {
super(params);
}
@Override
protected void onInitialize() {
super.onInitialize();
add(new EmailAddressesPanel("content", new AbstractReadOnlyModel<User>() {
@Override
public User getObject() {
return getUser();
}
}));
}
}

View File

@ -7,7 +7,7 @@
<input wicket:id="filterGroups" class="form-control search" placeholder="Filter by name...">
</div>
<input wicket:id="addNew" type="hidden" class="form-control mr-3 mb-4">
<a wicket:id="delete" class="btn btn-light-danger mb-4">Delete</a>
<a wicket:id="delete" class="btn btn-light-danger mb-4">Delete <wicket:svg href="arrow" class="rotate-90 icon"/></a>
</div>
</div>
<table wicket:id="memberships" class="memberships table"></table>

View File

@ -21,9 +21,9 @@ public class UserPasswordPage extends UserPage {
if (getUser().getPassword().equals(User.EXTERNAL_MANAGED)) {
String message;
if (getUser().getSsoInfo().getConnector() != null) {
if (getUser().getSsoConnector() != null) {
message = "The user is currently authenticated via SSO provider '"
+ getUser().getSsoInfo().getConnector()
+ getUser().getSsoConnector()
+ "', please change password there instead";
} else {
message = "The user is currently authenticated via external system, "

View File

@ -30,9 +30,9 @@ public class UserTwoFactorAuthenticationPage extends UserPage {
protected void onInitialize() {
super.onInitialize();
if (getUser().getSsoInfo().getConnector() != null) {
if (getUser().getSsoConnector() != null) {
add(new Label("content", "This account is currently authenticated via SSO provider '"
+ getUser().getSsoInfo().getConnector() + "', "
+ getUser().getSsoConnector() + "', "
+ "and two-factor authentication should be configured there")
.add(AttributeAppender.append("class", "alert alert-light-warning alert-notice mb-0")));
} else if (getUser().getTwoFactorAuthentication() != null) {

View File

@ -132,6 +132,15 @@ ul.feedbackPanel>li:first-child:last-child {
border-radius: 0 0.42rem 0.42rem 0;
}
.form-group .feedbackPanel {
margin: 0.8rem 0 !important;
}
.form-group .feedbackPanelERROR {
background-color: inherit !important;
border-left: none !important;
padding: 0 !important;
}
table {
border-spacing: 0;
}
@ -166,6 +175,7 @@ tr.navigation>td {
/* TABLE COMPONENTS */
table .row-selector {
width: 1px;
padding-top: 14px;
}
table .row-selector .checkbox span {
margin: 0;

View File

@ -49,11 +49,26 @@
<a href="#" class="dropdown-toggle user-info no-dropdown-caret topbar-link" data-toggle="dropdown">
<img wicket:id="avatar">
<span wicket:id="name" class="ml-2 d-none d-lg-inline"></span>
<span wicket:id="warningIcon"><wicket:svg href="warning-o" class="icon text-warning ml-2"/></span>
</a>
<div class="dropdown-menu">
<wicket:enclosure child="hasUnverifiedLink">
<div class="mx-3 my-2 alert alert-light-warning">
You have unverified <a wicket:id="hasUnverifiedLink">email addresses</a>
</div>
</wicket:enclosure>
<wicket:enclosure child="noPrimaryAddressLink">
<div class="mx-3 my-2 alert alert-light-warning">
Primary <a wicket:id="noPrimaryAddressLink">email address</a> not specified
</div>
</wicket:enclosure>
<a wicket:id="myProfile" class="dropdown-item">
<wicket:svg href="profile" class="icon mr-2"></wicket:svg>
My Profile
Profile
</a>
<a wicket:id="myEmailSetting" class="dropdown-item">
<wicket:svg href="mail" class="icon mr-2"></wicket:svg>
Email Addresses
</a>
<a wicket:id="myAvatar" class="dropdown-item">
<wicket:svg href="avatar" class="icon mr-2"></wicket:svg>

View File

@ -82,6 +82,7 @@ import io.onedev.server.web.page.help.IncompatibilitiesPage;
import io.onedev.server.web.page.my.MyPage;
import io.onedev.server.web.page.my.accesstoken.MyAccessTokenPage;
import io.onedev.server.web.page.my.avatar.MyAvatarPage;
import io.onedev.server.web.page.my.emailaddresses.MyEmailAddressesPage;
import io.onedev.server.web.page.my.password.MyPasswordPage;
import io.onedev.server.web.page.my.profile.MyProfilePage;
import io.onedev.server.web.page.my.sshkeys.MySshKeysPage;
@ -354,9 +355,24 @@ public abstract class LayoutPage extends BasePage {
if (loginUser != null) {
userInfo.add(new UserAvatar("avatar", loginUser));
userInfo.add(new Label("name", loginUser.getDisplayName()));
if (loginUser.getEmailAddresses().isEmpty()) {
userInfo.add(new WebMarkupContainer("warningIcon"));
userInfo.add(new WebMarkupContainer("hasUnverifiedLink").setVisible(false));
userInfo.add(new ViewStateAwarePageLink<Void>("noPrimaryAddressLink", MyEmailAddressesPage.class));
} else if (loginUser.getEmailAddresses().stream().anyMatch(it->!it.isVerified())) {
userInfo.add(new WebMarkupContainer("warningIcon"));
userInfo.add(new ViewStateAwarePageLink<Void>("hasUnverifiedLink", MyEmailAddressesPage.class));
userInfo.add(new WebMarkupContainer("noPrimaryAddressLink").setVisible(false));
} else {
userInfo.add(new WebMarkupContainer("warningIcon").setVisible(false));
userInfo.add(new WebMarkupContainer("hasUnverifiedLink").setVisible(false));
userInfo.add(new WebMarkupContainer("noPrimaryAddressLink").setVisible(false));
}
} else {
userInfo.add(new WebMarkupContainer("avatar"));
userInfo.add(new WebMarkupContainer("name"));
userInfo.add(new WebMarkupContainer("warningIcon"));
userInfo.add(new WebMarkupContainer("warningLink"));
}
WebMarkupContainer item;
@ -364,6 +380,10 @@ public abstract class LayoutPage extends BasePage {
if (getPage() instanceof MyProfilePage)
item.add(AttributeAppender.append("class", "active"));
userInfo.add(item = new ViewStateAwarePageLink<Void>("myEmailSetting", MyEmailAddressesPage.class));
if (getPage() instanceof MyEmailAddressesPage)
item.add(AttributeAppender.append("class", "active"));
userInfo.add(item = new ViewStateAwarePageLink<Void>("myAvatar", MyAvatarPage.class));
if (getPage() instanceof MyAvatarPage)
item.add(AttributeAppender.append("class", "active"));

View File

@ -0,0 +1,7 @@
<wicket:extend>
<div class="card m-2 m-sm-5">
<div class="card-body">
<div wicket:id="content"></div>
</div>
</div>
</wicket:extend>

View File

@ -0,0 +1,37 @@
package io.onedev.server.web.page.my.emailaddresses;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import io.onedev.server.model.User;
import io.onedev.server.web.component.user.emailaddresses.EmailAddressesPanel;
import io.onedev.server.web.page.my.MyPage;
@SuppressWarnings("serial")
public class MyEmailAddressesPage extends MyPage {
public MyEmailAddressesPage(PageParameters params) {
super(params);
}
@Override
protected void onInitialize() {
super.onInitialize();
add(new EmailAddressesPanel("content", new AbstractReadOnlyModel<User>() {
@Override
public User getObject() {
return getLoginUser();
}
}));
}
@Override
protected Component newTopbarTitle(String componentId) {
return new Label(componentId, "My Email Addresses");
}
}

View File

@ -23,9 +23,9 @@ public class MyPasswordPage extends MyPage {
if (getLoginUser().getPassword().equals(User.EXTERNAL_MANAGED)) {
String message;
if (getLoginUser().getSsoInfo().getConnector() != null) {
if (getLoginUser().getSsoConnector() != null) {
message = "You are currently authenticated via SSO provider '"
+ getLoginUser().getSsoInfo().getConnector()
+ getLoginUser().getSsoConnector()
+ "', please change password there instead";
} else {
message = "You are currently authenticated via external system, "

View File

@ -1,7 +1,7 @@
<wicket:extend>
<div class="card m-2 m-sm-5">
<div class="card-body">
<div wicket:id="externalManagedNote" class="alert alert-notice alert-light-warning"></div>
<div wicket:id="externalManagedNote" class="alert alert-warning alert-light-warning"></div>
<div wicket:id="content"></div>
<a wicket:id="delete" class="btn btn-light-danger">Delete</a>
</div>

View File

@ -18,7 +18,7 @@ import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
import io.onedev.server.web.component.modal.ModalLink;
import io.onedev.server.web.component.modal.ModalPanel;
import io.onedev.server.web.component.twofactorauthentication.TwoFactorAuthenticationSetupPanel;
import io.onedev.server.web.component.user.twofactorauthentication.TwoFactorAuthenticationSetupPanel;
import io.onedev.server.web.page.my.MyPage;
@SuppressWarnings("serial")
@ -36,9 +36,9 @@ public class MyTwoFactorAuthenticationPage extends MyPage {
protected void onInitialize() {
super.onInitialize();
if (getLoginUser().getSsoInfo().getConnector() != null) {
if (getLoginUser().getSsoConnector() != null) {
add(new Label("content", "You are currently authenticated via SSO provider '"
+ getLoginUser().getSsoInfo().getConnector() + "', "
+ getLoginUser().getSsoConnector() + "', "
+ "and two-factor authentication should be configured there")
.add(AttributeAppender.append("class", "alert alert-light-warning alert-notice mb-0")));
} else if (getLoginUser().getTwoFactorAuthentication() != null) {

View File

@ -285,7 +285,7 @@ public class CommitOptionPanel extends Panel {
});
}
while(newCommitId == null) {
try {
newCommitId = new BlobEdits(oldPaths, newBlobs).commit(repository, refName,

View File

@ -17,12 +17,13 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.DateUtils;
import io.onedev.server.util.facade.UserFacade;
import io.onedev.server.web.asset.emoji.Emojis;
import io.onedev.server.web.avatar.AvatarManager;
import io.onedev.server.web.page.project.commits.CommitDetailPage;
@ -67,7 +68,7 @@ class LastCommitsResource extends AbstractResource {
AvatarManager avatarManager = OneDev.getInstance(AvatarManager.class);
UserManager userManager = OneDev.getInstance(UserManager.class);
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
Map<String, LastCommitInfo> map = new HashMap<>();
for (Map.Entry<String, LastCommitsOfChildren.Value> entry: lastCommits.entrySet()) {
LastCommitInfo info = new LastCommitInfo();
@ -81,10 +82,15 @@ class LastCommitsResource extends AbstractResource {
info.when = DateUtils.formatAge(value.getCommitDate());
PersonIdent author = value.getAuthor();
UserFacade user = userManager.findFacadeByEmail(author.getEmailAddress());
if (user != null) {
info.authorName = user.getDisplayName();
info.authorEmailAddress = user.getEmail();
EmailAddress emailAddress = emailAddressManager.findByValue(author.getEmailAddress());
if (emailAddress != null && emailAddress.isVerified()) {
User owner = emailAddress.getOwner();
info.authorName = owner.getDisplayName();
EmailAddress primaryEmailAddress = owner.getPrimaryEmailAddress();
if (primaryEmailAddress != null && primaryEmailAddress.isVerified())
info.authorEmailAddress = primaryEmailAddress.getValue();
else
info.authorEmailAddress = author.getEmailAddress();
} else {
info.authorName = author.getName();
info.authorEmailAddress = author.getEmailAddress();

View File

@ -437,8 +437,7 @@ public class SourceViewPanel extends BlobViewPanel implements Positionable, Sear
for (User user: context.getProject().getAuthors(context.getBlobIdent().path, context.getCommit(),
new LinearRange(range.getFromRow(), range.getToRow()))) {
if (user.getEmail() != null)
mentions.append("@").append(user.getName()).append(" ");
mentions.append("@").append(user.getName()).append(" ");
}
form.add(contentInput = new CommentInput("content", Model.of(mentions.toString()), true) {

View File

@ -10,6 +10,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.Component;
@ -152,10 +153,14 @@ public class NewPullRequestPage extends ProjectPage implements RevisionDiff.Anno
private String suggestSourceBranch() {
User user = getLoginUser();
Collection<String> verifiedEmailAddresses = user.getEmailAddresses().stream()
.filter(it->it.isVerified())
.map(it->it.getValue())
.collect(Collectors.toSet());
List<Pair<String, Integer>> branchUpdates = new ArrayList<>();
for (RefInfo refInfo: getProject().getBranchRefInfos()) {
RevCommit commit = (RevCommit) refInfo.getPeeledObj();
if (commit.getAuthorIdent().getEmailAddress().equals(user.getEmail()))
if (verifiedEmailAddresses.contains(commit.getAuthorIdent().getEmailAddress().toLowerCase()))
branchUpdates.add(new Pair<>(GitUtils.ref2branch(refInfo.getRef().getName()), commit.getCommitTime()));
}
branchUpdates.sort(Comparator.comparing(Pair::getSecond));

View File

@ -0,0 +1,11 @@
<wicket:extend>
<div wicket:id="successful" class="alert alert-light-success font-size-lg font-weight-bolder">
<wicket:svg href="tick-circle" class="icon"/>
Your email address is now verified
</div>
<div wicket:id="failed" class="alert alert-light-danger">
<wicket:svg href="times-circle" class="icon"/>
Failed to verify your email address
</div>
<a wicket:id="goHome" class="btn btn-primary">Back To Home</a>
</wicket:extend>

View File

@ -0,0 +1,75 @@
package io.onedev.server.web.page.simple.security;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.web.page.simple.SimplePage;
@SuppressWarnings("serial")
public class EmailAddressVerificationPage extends SimplePage {
private final String PARAM_EMAIL_ADDRESS = "emailAddress";
private final String PARAM_VERIFICATION_CODE = "verificationCode";
private final IModel<EmailAddress> emailAddressModel;
public EmailAddressVerificationPage(PageParameters params) {
super(params);
Long emailAddressId = params.get(PARAM_EMAIL_ADDRESS).toLong();
emailAddressModel = new LoadableDetachableModel<EmailAddress>() {
@Override
protected EmailAddress load() {
return getEmailAddressManager().load(emailAddressId);
}
};
EmailAddress emailAddress = emailAddressModel.getObject();
String verificationCode = params.get(PARAM_VERIFICATION_CODE).toString();
if (verificationCode.equals(emailAddress.getVerficationCode())) {
emailAddress.setVerificationCode(null);
getEmailAddressManager().save(emailAddress);
}
}
private EmailAddressManager getEmailAddressManager() {
return OneDev.getInstance(EmailAddressManager.class);
}
@Override
protected void onInitialize() {
super.onInitialize();
EmailAddress emailAddress = emailAddressModel.getObject();
add(new WebMarkupContainer("successful").setVisible(emailAddress.isVerified()));
add(new WebMarkupContainer("failed").setVisible(!emailAddress.isVerified()));
add(new BookmarkablePageLink<Void>("goHome", getApplication().getHomePage()));
}
@Override
protected void onDetach() {
emailAddressModel.detach();
super.onDetach();
}
@Override
protected String getTitle() {
return "Email Address Verification";
}
@Override
protected String getSubTitle() {
return null;
}
}

View File

@ -39,7 +39,7 @@ import io.onedev.server.model.support.administration.sso.SsoConnector;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.realm.PasswordAuthorizingRealm;
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
import io.onedev.server.web.component.twofactorauthentication.TwoFactorAuthenticationSetupPanel;
import io.onedev.server.web.component.user.twofactorauthentication.TwoFactorAuthenticationSetupPanel;
import io.onedev.server.web.page.simple.SimpleCssResourceReference;
import io.onedev.server.web.page.simple.SimplePage;
@ -215,7 +215,7 @@ public class LoginPage extends SimplePage {
rememberMeManager.onSuccessfulLogin(SecurityUtils.getSubject(), token, user);
continueToOriginalDestination();
setResponsePage(getApplication().getHomePage());
throw new RestartResponseException(getApplication().getHomePage());
}
private void newTwoFactorAuthenticationSetup(Long userId) {

View File

@ -21,6 +21,7 @@ import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.notification.MailManager;
import io.onedev.server.persistence.SessionManager;
@ -76,9 +77,9 @@ public class PasswordResetPage extends SimplePage {
UserManager userManager = OneDev.getInstance(UserManager.class);
User user = userManager.findByName(loginNameOrEmail);
if (user == null)
user = userManager.findByEmail(loginNameOrEmail);
user = userManager.findByVerifiedEmailAddress(loginNameOrEmail);
if (user == null) {
throw new ExplicitException("No user found with login name or email: " + loginNameOrEmail);
throw new ExplicitException("No user found with login name or verified email: " + loginNameOrEmail);
} else {
SettingManager settingManager = OneDev.getInstance(SettingManager.class);
if (settingManager.getMailSetting() != null) {
@ -102,14 +103,27 @@ public class PasswordResetPage extends SimplePage {
+ "%s",
user.getDisplayName(), user.getName(), serverUrl, password);
String emailAddressValue;
if (loginNameOrEmail.contains("@")) {
emailAddressValue = loginNameOrEmail;
} else {
EmailAddress emailAddress = user.getPrimaryEmailAddress();
if (emailAddress == null)
throw new ExplicitException("Primary email address not specified");
else if (!emailAddress.isVerified())
throw new ExplicitException("Your primary email address is not verified");
else
emailAddressValue = emailAddress.getValue();
}
mailManager.sendMail(
settingManager.getMailSetting(),
Arrays.asList(user.getEmail()),
Arrays.asList(emailAddressValue),
Lists.newArrayList(), Lists.newArrayList(),
"[Password Reset] Your OneDev Password Has Been Reset",
htmlBody, textBody, null, null);
return "Please check your email " + user.getEmail() + " for the reset password";
return "Please check your email " + emailAddressValue + " for the reset password";
} else {
throw new ExplicitException("Unable to send password reset email as smtp setting is not defined");
}

View File

@ -8,13 +8,14 @@ import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import com.google.common.collect.Sets;
import io.onedev.commons.loader.AppLoader;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.EmailAddressManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.User;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.Path;
import io.onedev.server.util.PathNode;
@ -23,6 +24,7 @@ import io.onedev.server.web.editable.BeanEditor;
import io.onedev.server.web.page.my.avatar.MyAvatarPage;
import io.onedev.server.web.page.project.ProjectListPage;
import io.onedev.server.web.page.simple.SimplePage;
import io.onedev.server.web.util.NewUserBean;
@SuppressWarnings("serial")
public class SignUpPage extends SimplePage {
@ -40,9 +42,8 @@ public class SignUpPage extends SimplePage {
protected void onInitialize() {
super.onInitialize();
User user = new User();
BeanEditor editor = BeanContext.edit("editor", user,
Sets.newHashSet(User.PROP_GIT_EMAIL, User.PROP_ALTERNATE_EMAILS), true);
NewUserBean newUserBean = new NewUserBean();
BeanEditor editor = BeanContext.edit("editor", newUserBean);
Form<?> form = new Form<Void>("form") {
@ -51,19 +52,38 @@ public class SignUpPage extends SimplePage {
super.onSubmit();
UserManager userManager = OneDev.getInstance(UserManager.class);
User userWithSameName = userManager.findByName(user.getName());
User userWithSameName = userManager.findByName(newUserBean.getName());
if (userWithSameName != null) {
editor.error(new Path(new PathNode.Named(User.PROP_NAME)),
"Login name already used by another account");
}
User userWithSameEmail = userManager.findByEmail(user.getEmail());
if (userWithSameEmail != null) {
editor.error(new Path(new PathNode.Named(User.PROP_EMAIL)),
"Email already used by another account");
EmailAddressManager emailAddressManager = OneDev.getInstance(EmailAddressManager.class);
if (emailAddressManager.findByValue(newUserBean.getEmailAddress()) != null) {
editor.error(new Path(new PathNode.Named(NewUserBean.PROP_EMAIL_ADDRESS)),
"Email address already used by another user");
}
if (editor.isValid()) {
user.setPassword(AppLoader.getInstance(PasswordService.class).encryptPassword(user.getPassword()));
userManager.save(user, null);
User user = new User();
user.setName(newUserBean.getName());
user.setFullName(newUserBean.getFullName());
user.setPassword(AppLoader.getInstance(PasswordService.class).encryptPassword(newUserBean.getPassword()));
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue(newUserBean.getEmailAddress());
emailAddress.setOwner(user);
OneDev.getInstance(TransactionManager.class).run(new Runnable() {
@Override
public void run() {
userManager.save(user);
emailAddressManager.save(emailAddress);
}
});
Session.get().success("Welcome to OneDev");
SecurityUtils.getSubject().runAs(user.getPrincipals());
setResponsePage(MyAvatarPage.class);

View File

@ -0,0 +1,29 @@
package io.onedev.server.web.util;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.server.model.User;
import io.onedev.server.web.editable.annotation.Editable;
@Editable
public class NewUserBean extends User {
private static final long serialVersionUID = 1L;
public static final String PROP_EMAIL_ADDRESS = "emailAddress";
private String emailAddress;
@Editable(order=1000)
@NotEmpty
@Email
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}

View File

@ -15,7 +15,6 @@ import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.criterion.Restrictions;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
@ -51,7 +50,6 @@ import io.onedev.server.model.PullRequest;
import io.onedev.server.model.User;
import io.onedev.server.model.support.administration.GroovyScript;
import io.onedev.server.model.support.build.JobSecret;
import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.util.interpolative.VariableInterpolator;
@ -223,14 +221,7 @@ public class SuggestionUtils {
matchWith = matchWith.toLowerCase();
List<InputSuggestion> suggestions = new ArrayList<>();
EntityCriteria<User> criteria = EntityCriteria.of(User.class);
criteria.add(Restrictions.or(
Restrictions.ilike(User.PROP_NAME, "%" + matchWith + "%"),
Restrictions.ilike(User.PROP_EMAIL, "%" + matchWith + "%"),
Restrictions.ilike(User.PROP_FULL_NAME, "%" + matchWith + "%")));
criteria.add(Restrictions.gt("id", 0L));
for (User user: OneDev.getInstance(UserManager.class).query(criteria)) {
for (User user: OneDev.getInstance(UserManager.class).query(matchWith, 0, InputAssistBehavior.MAX_SUGGESTIONS)) {
LinearRange match = LinearRange.match(user.getName(), matchWith);
String description;
if (!user.getDisplayName().equals(user.getName()))
@ -238,8 +229,6 @@ public class SuggestionUtils {
else
description = null;
suggestions.add(new InputSuggestion(user.getName(), description, match));
if (suggestions.size() >= InputAssistBehavior.MAX_SUGGESTIONS)
break;
}
return suggestions;
}

View File

@ -10,6 +10,7 @@ import org.junit.Test;
import io.onedev.server.event.RefUpdated;
import io.onedev.server.git.AbstractGitTest;
import io.onedev.server.model.EmailAddress;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
@ -44,7 +45,14 @@ public class CommitQueryTest extends AbstractGitTest {
RefUpdated event = new RefUpdated(project, "refs/heads/master", oldCommitId, newCommitId);
User user = new User();
user.setEmail(CommitQueryTest.this.user.getEmailAddress());
EmailAddress emailAddress = new EmailAddress();
emailAddress.setGit(true);
emailAddress.setPrimary(true);
emailAddress.setOwner(user);
emailAddress.setVerificationCode(null);
emailAddress.setValue(CommitQueryTest.this.user.getEmailAddress());
user.getEmailAddresses().add(emailAddress);
User.push(user);
try {
assertTrue(CommitQuery.parse(project, null).matches(event));

View File

@ -94,7 +94,7 @@ public class ImportUtils {
if (userNode.hasNonNull("email"))
email = userNode.get("email").asText(null);
if (email != null)
userOpt = Optional.ofNullable(OneDev.getInstance(UserManager.class).findByEmail(email));
userOpt = Optional.ofNullable(OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email));
else
userOpt = Optional.empty();
users.put(login, userOpt);

View File

@ -85,7 +85,7 @@ public class ImportUtils {
String apiEndpoint = importSource.getApiEndpoint("/users/" + login);
String email = get(client, apiEndpoint, logger).get("email").asText(null);
if (email != null)
userOpt = Optional.ofNullable(OneDev.getInstance(UserManager.class).findByEmail(email));
userOpt = Optional.ofNullable(OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email));
else
userOpt = Optional.empty();
users.put(login, userOpt);

View File

@ -99,7 +99,7 @@ public class ImportUtils {
if (email == null && userNode.hasNonNull("public_email"))
email = userNode.get("public_email").asText(null);
if (email != null)
userOpt = Optional.ofNullable(OneDev.getInstance(UserManager.class).findByEmail(email));
userOpt = Optional.ofNullable(OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email));
else
userOpt = Optional.empty();
users.put(userId, userOpt);

View File

@ -301,7 +301,7 @@ public class ImportUtils {
String email = getEmail(reporterNode);
String login = reporterNode.get("login").asText();
if (email != null) {
User user = OneDev.getInstance(UserManager.class).findByEmail(email);
User user = OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email);
if (user != null) {
issue.setSubmitter(user);
} else {
@ -453,7 +453,7 @@ public class ImportUtils {
extraIssueInfo.put(fieldName, fullName);
} else {
if (email != null) {
User user = OneDev.getInstance(UserManager.class).findByEmail(email);
User user = OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email);
if (user != null) {
fieldValue = user.getName();
} else {
@ -494,7 +494,7 @@ public class ImportUtils {
String email = getEmail(valueNode);
if (email != null) {
User user = OneDev.getInstance(UserManager.class).findByEmail(email);
User user = OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email);
if (user != null) {
fieldValue = user.getName();
} else {
@ -727,7 +727,7 @@ public class ImportUtils {
String email = getEmail(authorNode);
String login = authorNode.get("login").asText();
if (email != null) {
User user = OneDev.getInstance(UserManager.class).findByEmail(email);
User user = OneDev.getInstance(UserManager.class).findByVerifiedEmailAddress(email);
if (user != null) {
comment.setUser(user);
} else {

View File

@ -74,7 +74,7 @@ public class GitHubConnector extends OpenIdConnector {
throw new AuthenticationException("A public email is required");
String fullName = (String) json.get("name");
return new SsoAuthenticated(userName, userName, email, fullName, null, null, this);
return new SsoAuthenticated(userName, email, fullName, null, null, this);
} else {
throw buildException(UserInfoErrorResponse.parse(httpResponse).getErrorObject());
}

Some files were not shown because too many files have changed in this diff Show More