mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
feat: multiple access token support with optional expiration (#568)
This commit is contained in:
parent
98a22fc621
commit
1a139bb783
@ -82,7 +82,9 @@ public interface UserManager extends EntityManager<User> {
|
||||
User findByFullName(String fullName);
|
||||
|
||||
@Nullable
|
||||
User findByAccessToken(String accessToken);
|
||||
User findByAccessToken(String accessTokenValue);
|
||||
|
||||
String createTemporalAccessToken(Long userId, long secondsToExpire);
|
||||
|
||||
@Nullable
|
||||
User findByVerifiedEmailAddress(String emailAddress);
|
||||
|
||||
@ -3,6 +3,7 @@ package io.onedev.server.entitymanager.impl;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.map.IMap;
|
||||
import io.onedev.server.cluster.ClusterManager;
|
||||
import io.onedev.server.entitymanager.*;
|
||||
import io.onedev.server.event.Listen;
|
||||
@ -36,6 +37,7 @@ import javax.inject.Singleton;
|
||||
import javax.persistence.criteria.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.onedev.server.model.User.*;
|
||||
|
||||
@ -62,6 +64,8 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
|
||||
|
||||
private volatile UserCache cache;
|
||||
|
||||
private volatile IMap<String, Long> temporalAccessTokens;
|
||||
|
||||
@Inject
|
||||
public DefaultUserManager(Dao dao, ProjectManager projectManager, SettingManager settingManager,
|
||||
IssueFieldManager issueFieldManager, IdManager idManager,
|
||||
@ -294,18 +298,30 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
|
||||
|
||||
@Sessional
|
||||
@Override
|
||||
public User findByAccessToken(String accessToken) {
|
||||
public User findByAccessToken(String accessTokenValue) {
|
||||
if (cache != null) {
|
||||
UserFacade facade = cache.findByAccessToken(accessToken);
|
||||
if (facade != null)
|
||||
UserFacade facade = cache.findByAccessToken(accessTokenValue);
|
||||
if (facade != null) {
|
||||
return load(facade.getId());
|
||||
else
|
||||
return null;
|
||||
} else {
|
||||
Long userId = temporalAccessTokens.get(accessTokenValue);
|
||||
if (userId != null)
|
||||
return load(userId);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
throw new ServerNotReadyException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createTemporalAccessToken(Long userId, long secondsToExpire) {
|
||||
var value = CryptoUtils.generateSecret();
|
||||
temporalAccessTokens.put(value, userId, secondsToExpire, TimeUnit.SECONDS);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> query() {
|
||||
EntityCriteria<User> criteria = newCriteria();
|
||||
@ -323,6 +339,7 @@ public class DefaultUserManager extends BaseEntityManager<User> implements UserM
|
||||
@Listen
|
||||
public void on(SystemStarting event) {
|
||||
HazelcastInstance hazelcastInstance = clusterManager.getHazelcastInstance();
|
||||
temporalAccessTokens = hazelcastInstance.getMap("temporalAccessTokens");
|
||||
cache = new UserCache(hazelcastInstance.getMap("userCache"));
|
||||
var cacheInited = hazelcastInstance.getCPSubsystem().getAtomicLong("userCacheInited");
|
||||
clusterManager.init(cacheInited, () -> {
|
||||
|
||||
@ -4,10 +4,12 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import io.onedev.k8shelper.KubernetesHelper;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.cluster.ClusterManager;
|
||||
import io.onedev.server.entitymanager.GitLfsLockManager;
|
||||
import io.onedev.server.entitymanager.ProjectManager;
|
||||
import io.onedev.server.entitymanager.SettingManager;
|
||||
import io.onedev.server.entitymanager.UserManager;
|
||||
import io.onedev.server.model.GitLfsLock;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.model.User;
|
||||
@ -285,8 +287,9 @@ public class GitLfsFilter implements Filter {
|
||||
httpResponse.setContentType(CONTENT_TYPE);
|
||||
if (pathInfo.endsWith("/batch")) {
|
||||
String accessToken;
|
||||
if (SecurityUtils.getUser() != null)
|
||||
accessToken = SecurityUtils.getUser().getAccessToken();
|
||||
var user = SecurityUtils.getUser();
|
||||
if (user != null)
|
||||
accessToken = OneDev.getInstance(UserManager.class).createTemporalAccessToken(user.getId(), 300);
|
||||
else
|
||||
accessToken = null;
|
||||
processBatch(httpRequest, httpResponse, project.getFacade(),
|
||||
|
||||
@ -91,36 +91,31 @@ public class LfsAuthenticateCommand implements Command, ServerSessionAware {
|
||||
public void start(ChannelSession channel, Environment env) throws IOException {
|
||||
SshAuthenticator authenticator = OneDev.getInstance(SshAuthenticator.class);
|
||||
Long userId = authenticator.getPublicKeyOwnerId(session);
|
||||
OneDev.getInstance(ExecutorService.class).submit(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SessionManager sessionManager = OneDev.getInstance(SessionManager.class);
|
||||
sessionManager.openSession();
|
||||
try {
|
||||
String accessToken = OneDev.getInstance(UserManager.class).load(userId).getAccessToken();
|
||||
String projectPath = StringUtils.strip(StringUtils.substringBefore(
|
||||
commandString.substring(COMMAND_PREFIX.length()+1), " "), "/\\");
|
||||
Project project = OneDev.getInstance(ProjectManager.class).findByPath(projectPath);
|
||||
if (project == null)
|
||||
throw new ExplicitException("Project not found: " + projectPath);
|
||||
String url = OneDev.getInstance(UrlManager.class).cloneUrlFor(project, false);
|
||||
Map<Object, Object> response = CollectionUtils.newHashMap(
|
||||
"href", url + ".git/info/lfs",
|
||||
"header", CollectionUtils.newHashMap(
|
||||
"Authorization", KubernetesHelper.BEARER + " " + accessToken));
|
||||
out.write(OneDev.getInstance(ObjectMapper.class).writeValueAsBytes(response));
|
||||
callback.onExit(0);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error executing " + COMMAND_PREFIX, e);
|
||||
new PrintStream(err).println("Check server log for details");
|
||||
callback.onExit(-1);
|
||||
} finally {
|
||||
sessionManager.closeSession();
|
||||
}
|
||||
OneDev.getInstance(ExecutorService.class).submit(() -> {
|
||||
SessionManager sessionManager = OneDev.getInstance(SessionManager.class);
|
||||
sessionManager.openSession();
|
||||
try {
|
||||
String accessToken = OneDev.getInstance(UserManager.class).createTemporalAccessToken(userId, 300);
|
||||
String projectPath = StringUtils.strip(StringUtils.substringBefore(
|
||||
commandString.substring(COMMAND_PREFIX.length()+1), " "), "/\\");
|
||||
Project project = OneDev.getInstance(ProjectManager.class).findByPath(projectPath);
|
||||
if (project == null)
|
||||
throw new ExplicitException("Project not found: " + projectPath);
|
||||
String url = OneDev.getInstance(UrlManager.class).cloneUrlFor(project, false);
|
||||
Map<Object, Object> response = CollectionUtils.newHashMap(
|
||||
"href", url + ".git/info/lfs",
|
||||
"header", CollectionUtils.newHashMap(
|
||||
"Authorization", KubernetesHelper.BEARER + " " + accessToken));
|
||||
out.write(OneDev.getInstance(ObjectMapper.class).writeValueAsBytes(response));
|
||||
callback.onExit(0);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error executing " + COMMAND_PREFIX, e);
|
||||
new PrintStream(err).println("Check server log for details");
|
||||
callback.onExit(-1);
|
||||
} finally {
|
||||
sessionManager.closeSession();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -5396,4 +5396,21 @@ public class DataMigrator {
|
||||
codeCommentTouchesDoc.writeToFile(new File(dataDir, "CodeCommentTouchs.xml"), true);
|
||||
}
|
||||
|
||||
private void migrate124(File dataDir, Stack<Integer> versions) {
|
||||
for (File file: dataDir.listFiles()) {
|
||||
if (file.getName().startsWith("Users.xml")) {
|
||||
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
|
||||
for (Element element: dom.getRootElement().elements()) {
|
||||
var accessTokenElement = element.element("accessToken");
|
||||
var accessTokensElement = element.addElement("accessTokens");
|
||||
var newAccessTokenElement = accessTokensElement.addElement("io.onedev.server.model.support.AccessToken");
|
||||
newAccessTokenElement.addElement("value").setText(accessTokenElement.getText().trim());
|
||||
newAccessTokenElement.addElement("createDate").setText("2023-05-28T22:07:56.311+01:00");
|
||||
accessTokenElement.detach();
|
||||
}
|
||||
dom.writeToFile(file, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -89,8 +89,8 @@ public class EmailAddress extends AbstractEntity {
|
||||
|
||||
@Override
|
||||
public EmailAddressFacade getFacade() {
|
||||
return new EmailAddressFacade(getId(), getValue(), isPrimary(), isGit(),
|
||||
getVerificationCode(), getOwner().getId());
|
||||
return new EmailAddressFacade(getId(), getOwner().getId(), getValue(),
|
||||
isPrimary(), isGit(), getVerificationCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import io.onedev.server.annotation.UserName;
|
||||
import io.onedev.server.entitymanager.EmailAddressManager;
|
||||
import io.onedev.server.entitymanager.SettingManager;
|
||||
import io.onedev.server.entitymanager.UserManager;
|
||||
import io.onedev.server.model.support.AccessToken;
|
||||
import io.onedev.server.model.support.NamedProjectQuery;
|
||||
import io.onedev.server.model.support.QueryPersonalization;
|
||||
import io.onedev.server.model.support.TwoFactorAuthentication;
|
||||
@ -20,7 +21,6 @@ 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.security.SecurityUtils;
|
||||
import io.onedev.server.util.CryptoUtils;
|
||||
import io.onedev.server.util.facade.UserFacade;
|
||||
import io.onedev.server.util.watch.QuerySubscriptionSupport;
|
||||
import io.onedev.server.util.watch.QueryWatchSupport;
|
||||
@ -43,7 +43,7 @@ import static io.onedev.server.model.User.*;
|
||||
@Entity
|
||||
@Table(
|
||||
indexes={@Index(columnList=PROP_NAME), @Index(columnList=PROP_FULL_NAME),
|
||||
@Index(columnList=PROP_SSO_CONNECTOR), @Index(columnList=PROP_ACCESS_TOKEN)})
|
||||
@Index(columnList=PROP_SSO_CONNECTOR)})
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
@Editable
|
||||
public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
@ -94,10 +94,11 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
|
||||
@JsonIgnore
|
||||
private String ssoConnector;
|
||||
|
||||
@Column(unique=true, nullable=false)
|
||||
|
||||
@JsonIgnore
|
||||
private String accessToken = CryptoUtils.generateSecret();
|
||||
@Lob
|
||||
@Column(length=65535)
|
||||
private ArrayList<AccessToken> accessTokens = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
@Lob
|
||||
@ -477,12 +478,12 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
this.ssoConnector = ssoConnector;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
public ArrayList<AccessToken> getAccessTokens() {
|
||||
return accessTokens;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
public void setAccessTokens(ArrayList<AccessToken> accessTokens) {
|
||||
this.accessTokens = accessTokens;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -850,7 +851,7 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
|
||||
@Override
|
||||
public UserFacade getFacade() {
|
||||
return new UserFacade(getId(), getName(), getFullName(), getAccessToken());
|
||||
return new UserFacade(getId(), getName(), getFullName(), getAccessTokens());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
package io.onedev.server.model.support;
|
||||
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.annotation.Multiline;
|
||||
import io.onedev.server.model.AbstractEntity;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.util.CryptoUtils;
|
||||
import io.onedev.server.util.facade.AccessTokenFacade;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Editable
|
||||
public class AccessToken implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String value = CryptoUtils.generateSecret();
|
||||
|
||||
private String description;
|
||||
|
||||
private Date createDate = new Date();
|
||||
|
||||
private Date expireDate;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Editable(order=100, description = "Optionally specify description of the token")
|
||||
@Multiline
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(@Nullable String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Date getCreateDate() {
|
||||
return createDate;
|
||||
}
|
||||
|
||||
public void setCreateDate(Date createDate) {
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
@Editable(order=200, placeholder = "Never expire", description = "Optionally specify " +
|
||||
"expiration date of the token. Leave empty to never expire")
|
||||
@Nullable
|
||||
public Date getExpireDate() {
|
||||
return expireDate;
|
||||
}
|
||||
|
||||
public void setExpireDate(@Nullable Date expireDate) {
|
||||
this.expireDate = expireDate;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return getExpireDate() != null && getExpireDate().before(new Date());
|
||||
}
|
||||
|
||||
public String getMaskedValue() {
|
||||
var maskedValue = new StringBuilder();
|
||||
for (int i=0; i<value.length(); i++) {
|
||||
var ch = value.charAt(i);
|
||||
if (i >= 6)
|
||||
maskedValue.append("*");
|
||||
else
|
||||
maskedValue.append(ch);
|
||||
}
|
||||
return maskedValue.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,6 +23,7 @@ import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import io.onedev.server.model.support.AccessToken;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.apache.shiro.authz.UnauthenticatedException;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
@ -104,13 +105,13 @@ public class UserResource {
|
||||
}
|
||||
|
||||
@Api(order=250)
|
||||
@Path("/{userId}/access-token")
|
||||
@Path("/{userId}/access-tokens")
|
||||
@GET
|
||||
public String getAccessToken(@PathParam("userId") Long userId) {
|
||||
public List<AccessToken> getAccessTokens(@PathParam("userId") Long userId) {
|
||||
User user = userManager.load(userId);
|
||||
if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
|
||||
throw new UnauthorizedException();
|
||||
return user.getAccessToken();
|
||||
return user.getAccessTokens();
|
||||
}
|
||||
|
||||
@Api(order=275)
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package io.onedev.server.util.facade;
|
||||
|
||||
public class AccessTokenFacade extends EntityFacade {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Long ownerId;
|
||||
|
||||
private final String value;
|
||||
|
||||
public AccessTokenFacade(Long id, Long ownerId, String value) {
|
||||
super(id);
|
||||
this.ownerId = ownerId;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Long getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,7 +5,9 @@ import javax.annotation.Nullable;
|
||||
public class EmailAddressFacade extends EntityFacade {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
private final Long ownerId;
|
||||
|
||||
private final String value;
|
||||
|
||||
private final boolean primary;
|
||||
@ -14,16 +16,18 @@ public class EmailAddressFacade extends EntityFacade {
|
||||
|
||||
private final String verificationCode;
|
||||
|
||||
private final Long ownerId;
|
||||
|
||||
public EmailAddressFacade(Long id, String value, boolean primary, boolean git,
|
||||
@Nullable String verificationCode, Long ownerId) {
|
||||
public EmailAddressFacade(Long id, Long ownerId, String value, boolean primary,
|
||||
boolean git, @Nullable String verificationCode) {
|
||||
super(id);
|
||||
this.ownerId = ownerId;
|
||||
this.value = value;
|
||||
this.primary = primary;
|
||||
this.git = git;
|
||||
this.verificationCode = verificationCode;
|
||||
this.ownerId = ownerId;
|
||||
}
|
||||
|
||||
public Long getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
@ -43,10 +47,6 @@ public class EmailAddressFacade extends EntityFacade {
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
public Long getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return getVerificationCode() == null;
|
||||
}
|
||||
|
||||
@ -43,10 +43,12 @@ public class UserCache extends MapProxy<Long, UserFacade> implements Serializabl
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UserFacade findByAccessToken(String accessToken) {
|
||||
public UserFacade findByAccessToken(String accessTokenValue) {
|
||||
for (UserFacade facade: values()) {
|
||||
if (accessToken.equals(facade.getAccessToken()))
|
||||
return facade;
|
||||
for (var accessToken: facade.getAccessTokens()) {
|
||||
if (accessTokenValue.equals(accessToken.getValue()) && !accessToken.isExpired())
|
||||
return facade;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package io.onedev.server.util.facade;
|
||||
|
||||
import io.onedev.server.model.support.AccessToken;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public class UserFacade extends EntityFacade {
|
||||
|
||||
@ -10,13 +13,13 @@ public class UserFacade extends EntityFacade {
|
||||
|
||||
private final String fullName;
|
||||
|
||||
private final String accessToken;
|
||||
private final List<AccessToken> accessTokens;
|
||||
|
||||
public UserFacade(Long id, String name, @Nullable String fullName, String accessToken) {
|
||||
public UserFacade(Long id, String name, @Nullable String fullName, List<AccessToken> accessTokens) {
|
||||
super(id);
|
||||
this.name = name;
|
||||
this.fullName = fullName;
|
||||
this.accessToken = accessToken;
|
||||
this.accessTokens = accessTokens;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@ -27,8 +30,8 @@ public class UserFacade extends EntityFacade {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
public List<AccessToken> getAccessTokens() {
|
||||
return accessTokens;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
<wicket:panel>
|
||||
<form wicket:id="form" class="leave-confirm border rounded">
|
||||
<div class="border-bottom px-4 py-3">
|
||||
<span wicket:id="value" class="font-weight-bolder text-monospace"></span>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div wicket:id="editor"></div>
|
||||
<div class="actions">
|
||||
<button wicket:id="save" class="btn btn-primary">Save</button>
|
||||
<button wicket:id="cancel" class="btn btn-secondary">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</wicket:panel>
|
||||
@ -0,0 +1,63 @@
|
||||
package io.onedev.server.web.component.user.accesstoken;
|
||||
|
||||
import io.onedev.server.model.support.AccessToken;
|
||||
import io.onedev.server.web.editable.BeanContext;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
abstract class AccessTokenEditPanel extends Panel {
|
||||
|
||||
private final AccessToken accessToken;
|
||||
|
||||
public AccessTokenEditPanel(String id, AccessToken accessToken) {
|
||||
super(id);
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
Form<?> form = new Form<Void>("form");
|
||||
|
||||
form.add(new Label("value", accessToken.getMaskedValue()));
|
||||
form.add(BeanContext.edit("editor", accessToken));
|
||||
|
||||
form.add(new AjaxButton("save") {
|
||||
|
||||
@Override
|
||||
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
|
||||
super.onSubmit(target, form);
|
||||
onSave(target, accessToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onError(AjaxRequestTarget target, Form<?> form) {
|
||||
super.onError(target, form);
|
||||
target.add(form);
|
||||
}
|
||||
|
||||
});
|
||||
form.add(new AjaxLink<Void>("cancel") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
onCancel(target);
|
||||
}
|
||||
|
||||
});
|
||||
add(form);
|
||||
|
||||
setOutputMarkupId(true);
|
||||
}
|
||||
|
||||
protected abstract void onSave(AjaxRequestTarget target, AccessToken accessToken);
|
||||
|
||||
protected abstract void onCancel(AjaxRequestTarget target);
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
<wicket:panel>
|
||||
<div wicket:id="accessTokens" class="access-tokens">
|
||||
<ul class="list-unstyled">
|
||||
<li wicket:id="accessTokens" class="access-token mb-5"><div wicket:id="accessToken"></div></li>
|
||||
</ul>
|
||||
<div wicket:id="newAccessToken"></div>
|
||||
</div>
|
||||
<wicket:fragment wicket:id="addNewLinkFrag">
|
||||
<a wicket:id="link" class="btn btn-block btn-light"><wicket:svg href="plus" class="icon align-middle mr-1"></wicket:svg> <span>Create New</span></a>
|
||||
</wicket:fragment>
|
||||
</wicket:panel>
|
||||
@ -0,0 +1,132 @@
|
||||
package io.onedev.server.web.component.user.accesstoken;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.entitymanager.UserManager;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.support.AccessToken;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.markup.html.panel.Fragment;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.AbstractReadOnlyModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AccessTokenListPanel extends Panel {
|
||||
|
||||
private WebMarkupContainer container;
|
||||
|
||||
public AccessTokenListPanel(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
protected abstract User getUser();
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
container = new WebMarkupContainer("accessTokens");
|
||||
container.setOutputMarkupId(true);
|
||||
add(container);
|
||||
container.add(new ListView<>("accessTokens", new AbstractReadOnlyModel<List<AccessToken>>() {
|
||||
|
||||
@Override
|
||||
public List<AccessToken> getObject() {
|
||||
return getUser().getAccessTokens();
|
||||
}
|
||||
}) {
|
||||
|
||||
private Component newViewer(String componentId, int index, AccessToken accessToken) {
|
||||
return new AccessTokenPanel(componentId, accessToken) {
|
||||
|
||||
@Override
|
||||
protected void onDelete(AjaxRequestTarget target) {
|
||||
getUser().getAccessTokens().remove(index);
|
||||
getUserManager().update(getUser(), null);
|
||||
target.add(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEdit(AjaxRequestTarget target) {
|
||||
AccessTokenEditPanel editor = new AccessTokenEditPanel("accessToken", accessToken) {
|
||||
|
||||
private void view(AjaxRequestTarget target) {
|
||||
Component viewer = newViewer(componentId, index, accessToken);
|
||||
replaceWith(viewer);
|
||||
target.add(viewer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSave(AjaxRequestTarget target, AccessToken accessToken) {
|
||||
getUser().getAccessTokens().set(index, accessToken);
|
||||
getUserManager().update(getUser(), null);
|
||||
view(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel(AjaxRequestTarget target) {
|
||||
view(target);
|
||||
}
|
||||
|
||||
};
|
||||
replaceWith(editor);
|
||||
target.add(editor);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(final ListItem<AccessToken> item) {
|
||||
item.add(newViewer("accessToken", item.getIndex(), item.getModelObject()));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
container.add(newAddNewFrag());
|
||||
}
|
||||
|
||||
private Component newAddNewFrag() {
|
||||
Fragment fragment = new Fragment("newAccessToken", "addNewLinkFrag", this);
|
||||
fragment.add(new AjaxLink<Void>("link") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
Component editor = new AccessTokenEditPanel("newAccessToken", new AccessToken()) {
|
||||
|
||||
@Override
|
||||
protected void onSave(AjaxRequestTarget target, AccessToken accessToken) {
|
||||
getUser().getAccessTokens().add(accessToken);
|
||||
getUserManager().update(getUser(), null);
|
||||
container.replace(newAddNewFrag());
|
||||
target.add(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel(AjaxRequestTarget target) {
|
||||
Component newAddNewFrag = newAddNewFrag();
|
||||
container.replace(newAddNewFrag);
|
||||
target.add(newAddNewFrag);
|
||||
}
|
||||
|
||||
};
|
||||
container.replace(editor);
|
||||
target.add(editor);
|
||||
}
|
||||
|
||||
});
|
||||
fragment.setOutputMarkupId(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private UserManager getUserManager() {
|
||||
return OneDev.getInstance(UserManager.class);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,11 +1,24 @@
|
||||
<wicket:panel>
|
||||
<div class="access-token">
|
||||
<div class="input-group">
|
||||
<input wicket:id="value" class="value form-control" readonly="readonly" type="password">
|
||||
<span class="input-group-append">
|
||||
<a wicket:id="copy" class="btn btn-outline-secondary btn-icon"><wicket:svg href="copy" class="icon"></wicket:svg></a>
|
||||
</span>
|
||||
<div class="access-token border rounded">
|
||||
<div class="border-bottom px-4 py-3">
|
||||
<span wicket:id="value" class="text-monospace font-weight-bolder mr-2"></span>
|
||||
<a wicket:id="copy" class="btn btn-xs btn-icon btn-light btn-hover-primary mr-2" title="Copy to clipboard"><wicket:svg href="copy" class="icon"></wicket:svg></a>
|
||||
<a wicket:id="edit" class="btn btn-xs btn-icon btn-light btn-hover-primary mr-2" title="Edit this rule"><wicket:svg href="edit" class="icon"></wicket:svg></a>
|
||||
<a wicket:id="delete" class="btn btn-xs btn-icon btn-light btn-hover-danger mr-4" title="Delete this rule"><wicket:svg href="trash" class="icon"></wicket:svg></a>
|
||||
</div>
|
||||
<div class="px-4 pb-4">
|
||||
<div class="mt-4">
|
||||
<label class="font-weight-bolder">Description</label>
|
||||
<div wicket:id="description"></div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="font-weight-bolder">Created At</label>
|
||||
<div wicket:id="createdAt"></div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="font-weight-bolder">Expires At</label>
|
||||
<div wicket:id="expiresAt"></div>
|
||||
</div>
|
||||
</div>
|
||||
<a wicket:id="regenerate" class="btn btn-primary mt-4">Regenerate</a>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
@ -1,61 +1,85 @@
|
||||
package io.onedev.server.web.component.user.accesstoken;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.entitymanager.UserManager;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.util.CryptoUtils;
|
||||
import io.onedev.server.model.support.AccessToken;
|
||||
import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
|
||||
import io.onedev.server.web.component.MultilineLabel;
|
||||
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
||||
import io.onedev.server.web.util.ConfirmClickModifier;
|
||||
import org.apache.wicket.Session;
|
||||
import org.apache.wicket.markup.html.form.TextField;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.behavior.AttributeAppender;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.AbstractReadOnlyModel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static io.onedev.server.util.DateUtils.formatDate;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AccessTokenPanel extends Panel {
|
||||
abstract class AccessTokenPanel extends Panel {
|
||||
|
||||
public AccessTokenPanel(String id) {
|
||||
private final AccessToken accessToken;
|
||||
|
||||
public AccessTokenPanel(String id, AccessToken accessToken) {
|
||||
super(id);
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
protected abstract User getUser();
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
IModel<String> valueModel = new AbstractReadOnlyModel<String>() {
|
||||
|
||||
add(new Label("value", accessToken.getMaskedValue()));
|
||||
add(new CopyToClipboardLink("copy", Model.of(accessToken.getValue())));
|
||||
add(new AjaxLink<Void>("edit") {
|
||||
|
||||
@Override
|
||||
public String getObject() {
|
||||
return getUser().getAccessToken();
|
||||
}
|
||||
|
||||
};
|
||||
add(new TextField<String>("value", valueModel) {
|
||||
|
||||
@Override
|
||||
protected String[] getInputTypes() {
|
||||
return new String[] {"password"};
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
onEdit(target);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
add(new CopyToClipboardLink("copy", valueModel));
|
||||
|
||||
add(new Link<Void>("regenerate") {
|
||||
add(new AjaxLink<Void>("delete") {
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
getUser().setAccessToken(CryptoUtils.generateSecret());
|
||||
OneDev.getInstance(UserManager.class).update(getUser(), null);
|
||||
Session.get().success("Access token regenerated");
|
||||
setResponsePage(getPage());
|
||||
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
|
||||
super.updateAjaxAttributes(attributes);
|
||||
attributes.getAjaxCallListeners().add(new ConfirmClickListener("Do you really want to delete this access token?"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
onDelete(target);
|
||||
}
|
||||
|
||||
}.add(new ConfirmClickModifier("This will invalidate current token and generate a new one, do you want to continue?")));
|
||||
});
|
||||
|
||||
if (accessToken.getDescription() != null)
|
||||
add(new MultilineLabel("description", accessToken.getDescription()));
|
||||
else
|
||||
add(new Label("description", "<i>No description</i>").setEscapeModelStrings(false));
|
||||
|
||||
add(new Label("createdAt", formatDate(accessToken.getCreateDate())));
|
||||
|
||||
var expireDate = accessToken.getExpireDate();
|
||||
if (expireDate != null) {
|
||||
if (expireDate.before(new Date())) {
|
||||
add(new Label("expiresAt", formatDate(expireDate))
|
||||
.add(AttributeAppender.append("class", "text-danger")));
|
||||
} else {
|
||||
add(new Label("expiresAt", formatDate(expireDate)));
|
||||
}
|
||||
} else {
|
||||
add(new Label("expiresAt", "Never expires"));
|
||||
}
|
||||
|
||||
setOutputMarkupId(true);
|
||||
}
|
||||
|
||||
protected abstract void onDelete(AjaxRequestTarget target);
|
||||
|
||||
protected abstract void onEdit(AjaxRequestTarget target);
|
||||
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ import io.onedev.server.web.page.admin.usermanagement.InvitationListPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.NewInvitationPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.NewUserPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.UserListPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.accesstoken.UserAccessTokenPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.accesstoken.UserAccessTokensPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.authorization.UserAuthorizationsPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.avatar.UserAvatarPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.emailaddresses.UserEmailAddressesPage;
|
||||
@ -65,7 +65,7 @@ import io.onedev.server.web.page.help.MethodDetailPage;
|
||||
import io.onedev.server.web.page.help.ResourceDetailPage;
|
||||
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.accesstoken.MyAccessTokensPage;
|
||||
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.gpgkeys.MyGpgKeysPage;
|
||||
@ -174,7 +174,7 @@ public class BaseUrlMapper extends CompoundRequestMapper {
|
||||
add(new BasePageMapper("~my/password", MyPasswordPage.class));
|
||||
add(new BasePageMapper("~my/ssh-keys", MySshKeysPage.class));
|
||||
add(new BasePageMapper("~my/gpg-keys", MyGpgKeysPage.class));
|
||||
add(new BasePageMapper("~my/access-token", MyAccessTokenPage.class));
|
||||
add(new BasePageMapper("~my/access-tokens", MyAccessTokensPage.class));
|
||||
add(new BasePageMapper("~my/two-factor-authentication", MyTwoFactorAuthenticationPage.class));
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ public class BaseUrlMapper extends CompoundRequestMapper {
|
||||
add(new BasePageMapper("~administration/users/${user}/password", UserPasswordPage.class));
|
||||
add(new BasePageMapper("~administration/users/${user}/ssh-keys", UserSshKeysPage.class));
|
||||
add(new BasePageMapper("~administration/users/${user}/gpg-keys", UserGpgKeysPage.class));
|
||||
add(new BasePageMapper("~administration/users/${user}/access-token", UserAccessTokenPage.class));
|
||||
add(new BasePageMapper("~administration/users/${user}/access-tokens", UserAccessTokensPage.class));
|
||||
add(new BasePageMapper("~administration/users/${user}/two-factor-authentication",
|
||||
UserTwoFactorAuthenticationPage.class));
|
||||
add(new BasePageMapper("~administration/invitations", InvitationListPage.class));
|
||||
|
||||
@ -23,7 +23,7 @@ import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.tabbable.PageTab;
|
||||
import io.onedev.server.web.component.tabbable.Tabbable;
|
||||
import io.onedev.server.web.page.admin.AdministrationPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.accesstoken.UserAccessTokenPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.accesstoken.UserAccessTokensPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.authorization.UserAuthorizationsPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.avatar.UserAvatarPage;
|
||||
import io.onedev.server.web.page.admin.usermanagement.emailaddresses.UserEmailAddressesPage;
|
||||
@ -76,7 +76,7 @@ public abstract class UserPage extends AdministrationPage {
|
||||
tabs.add(new UserTab("Authorized Projects", "project", UserAuthorizationsPage.class));
|
||||
tabs.add(new UserTab("SSH Keys", "key", UserSshKeysPage.class));
|
||||
tabs.add(new UserTab("GPG Keys", "key", UserGpgKeysPage.class));
|
||||
tabs.add(new UserTab("Access Token", "token", UserAccessTokenPage.class));
|
||||
tabs.add(new UserTab("Access Tokens", "token", UserAccessTokensPage.class));
|
||||
tabs.add(new UserTab("Two-factor Authentication", "shield", UserTwoFactorAuthenticationPage.class));
|
||||
|
||||
add(new Tabbable("userTabs", tabs));
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
<wicket:extend>
|
||||
<div id="user-access-token">
|
||||
<div class="font-size-sm text-muted mb-4"><wicket:svg href="bulb" class="icon"/> Access token
|
||||
can be used to clone code via HTTP(S) protocol without using password. It has same permission
|
||||
over all projects accessible by this user</div>
|
||||
<div wicket:id="accessToken"></div>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
@ -0,0 +1,9 @@
|
||||
<wicket:extend>
|
||||
<div id="user-access-tokens">
|
||||
<div class="alert alert-notice alert-light mb-5">
|
||||
Access token can be used to clone code via HTTP(S) protocol without using password.
|
||||
It has same permission over all projects accessible by this user
|
||||
</div>
|
||||
<div wicket:id="accessTokens"></div>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
@ -3,13 +3,13 @@ package io.onedev.server.web.page.admin.usermanagement.accesstoken;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.user.accesstoken.AccessTokenPanel;
|
||||
import io.onedev.server.web.component.user.accesstoken.AccessTokenListPanel;
|
||||
import io.onedev.server.web.page.admin.usermanagement.UserPage;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class UserAccessTokenPage extends UserPage {
|
||||
public class UserAccessTokensPage extends UserPage {
|
||||
|
||||
public UserAccessTokenPage(PageParameters params) {
|
||||
public UserAccessTokensPage(PageParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
@ -17,11 +17,11 @@ public class UserAccessTokenPage extends UserPage {
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new AccessTokenPanel("accessToken") {
|
||||
add(new AccessTokenListPanel("accessTokens") {
|
||||
|
||||
@Override
|
||||
protected User getUser() {
|
||||
return UserAccessTokenPage.this.getUser();
|
||||
return UserAccessTokensPage.this.getUser();
|
||||
}
|
||||
|
||||
});
|
||||
@ -87,9 +87,9 @@
|
||||
<wicket:svg href="key" class="icon mr-2"></wicket:svg>
|
||||
GPG Keys
|
||||
</a>
|
||||
<a wicket:id="myAccessToken" class="dropdown-item">
|
||||
<a wicket:id="myAccessTokens" class="dropdown-item">
|
||||
<wicket:svg href="token" class="icon mr-2"></wicket:svg>
|
||||
Access Token
|
||||
Access Tokens
|
||||
</a>
|
||||
<a wicket:id="myTwoFactorAuthentication" class="dropdown-item">
|
||||
<wicket:svg href="shield" class="icon mr-2"></wicket:svg>
|
||||
|
||||
@ -59,7 +59,7 @@ import io.onedev.server.web.page.admin.usermanagement.*;
|
||||
import io.onedev.server.web.page.base.BasePage;
|
||||
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.accesstoken.MyAccessTokensPage;
|
||||
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.gpgkeys.MyGpgKeysPage;
|
||||
@ -70,7 +70,6 @@ import io.onedev.server.web.page.my.twofactorauthentication.MyTwoFactorAuthentic
|
||||
import io.onedev.server.web.page.simple.security.LoginPage;
|
||||
import io.onedev.server.web.page.simple.security.LogoutPage;
|
||||
import io.onedev.server.web.util.WicketUtils;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.RestartResponseAtInterceptPageException;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
@ -507,8 +506,8 @@ public abstract class LayoutPage extends BasePage {
|
||||
if (getPage() instanceof MyGpgKeysPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myAccessToken", MyAccessTokenPage.class));
|
||||
if (getPage() instanceof MyAccessTokenPage)
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myAccessTokens", MyAccessTokensPage.class));
|
||||
if (getPage() instanceof MyAccessTokensPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myTwoFactorAuthentication", MyTwoFactorAuthenticationPage.class));
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<wicket:extend>
|
||||
<div class="card m-2 m-sm-5">
|
||||
<div class="card-body">
|
||||
<div class="font-size-sm text-muted mb-4">
|
||||
<wicket:svg href="bulb" class="icon"/> Access token can be used to clone code via HTTP(S) protocol without
|
||||
<div class="alert alert-notice alert-light mb-5">
|
||||
Access token can be used to clone code via HTTP(S) protocol without
|
||||
using password. It has same permission over all projects accessible by your account.
|
||||
</div>
|
||||
<div wicket:id="accessToken"></div>
|
||||
<div wicket:id="accessTokens"></div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
@ -5,13 +5,13 @@ import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.user.accesstoken.AccessTokenPanel;
|
||||
import io.onedev.server.web.component.user.accesstoken.AccessTokenListPanel;
|
||||
import io.onedev.server.web.page.my.MyPage;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class MyAccessTokenPage extends MyPage {
|
||||
public class MyAccessTokensPage extends MyPage {
|
||||
|
||||
public MyAccessTokenPage(PageParameters params) {
|
||||
public MyAccessTokensPage(PageParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ public class MyAccessTokenPage extends MyPage {
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new AccessTokenPanel("accessToken") {
|
||||
add(new AccessTokenListPanel("accessTokens") {
|
||||
|
||||
@Override
|
||||
protected User getUser() {
|
||||
@ -32,7 +32,7 @@ public class MyAccessTokenPage extends MyPage {
|
||||
|
||||
@Override
|
||||
protected Component newTopbarTitle(String componentId) {
|
||||
return new Label(componentId, "My Access Token");
|
||||
return new Label(componentId, "My Access Tokens");
|
||||
}
|
||||
|
||||
}
|
||||
@ -51,14 +51,14 @@ public class BranchProtectionsPage extends ProjectSettingPage {
|
||||
@Override
|
||||
protected void onDelete(AjaxRequestTarget target) {
|
||||
getProject().getBranchProtections().remove(item.getIndex());
|
||||
OneDev.getInstance(ProjectManager.class).update(getProject());
|
||||
getProjectManager().update(getProject());
|
||||
target.add(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSave(AjaxRequestTarget target, BranchProtection protection) {
|
||||
getProject().getBranchProtections().set(item.getIndex(), protection);
|
||||
OneDev.getInstance(ProjectManager.class).update(getProject());
|
||||
getProjectManager().update(getProject());
|
||||
}
|
||||
|
||||
});
|
||||
@ -73,7 +73,7 @@ public class BranchProtectionsPage extends ProjectSettingPage {
|
||||
List<BranchProtection> protections = getProject().getBranchProtections();
|
||||
BranchProtection protection = protections.get(from.getItemIndex());
|
||||
protections.set(from.getItemIndex(), protections.set(to.getItemIndex(), protection));
|
||||
OneDev.getInstance(ProjectManager.class).update(getProject());
|
||||
getProjectManager().update(getProject());
|
||||
|
||||
target.add(container);
|
||||
}
|
||||
@ -96,7 +96,7 @@ public class BranchProtectionsPage extends ProjectSettingPage {
|
||||
@Override
|
||||
protected void onSave(AjaxRequestTarget target, BranchProtection protection) {
|
||||
getProject().getBranchProtections().add(protection);
|
||||
OneDev.getInstance(ProjectManager.class).update(getProject());
|
||||
getProjectManager().update(getProject());
|
||||
container.replace(newAddNewFrag());
|
||||
target.add(container);
|
||||
}
|
||||
@ -117,7 +117,7 @@ public class BranchProtectionsPage extends ProjectSettingPage {
|
||||
fragment.setOutputMarkupId(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Component newProjectTitle(String componentId) {
|
||||
return new Label(componentId, "Branch Protection");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user