@ -20,7 +20,9 @@ import com.google.common.collect.Ordering;
import com.google.common.collect.Streams ;
import lombok.Data ;
import lombok.Getter ;
import org.thingsboard.server.common.data.StringUtils ;
import lombok.RequiredArgsConstructor ;
import org.apache.commons.io.FileUtils ;
import org.apache.commons.lang3.StringUtils ;
import org.apache.sshd.common.util.security.SecurityUtils ;
import org.eclipse.jgit.api.CloneCommand ;
import org.eclipse.jgit.api.Git ;
@ -49,6 +51,7 @@ import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult ;
import org.eclipse.jgit.transport.RefSpec ;
import org.eclipse.jgit.transport.SshTransport ;
import org.eclipse.jgit.transport.URIish ;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider ;
import org.eclipse.jgit.transport.sshd.JGitKeyCache ;
import org.eclipse.jgit.transport.sshd.ServerKeyDatabase ;
@ -61,8 +64,8 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink ;
import org.thingsboard.server.common.data.page.SortOrder ;
import org.thingsboard.server.common.data.sync.vc.BranchInfo ;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings ;
import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod ;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings ;
import java.io.ByteArrayInputStream ;
import java.io.ByteArrayOutputStream ;
@ -70,6 +73,7 @@ import java.io.File;
import java.io.IOException ;
import java.net.InetSocketAddress ;
import java.nio.charset.StandardCharsets ;
import java.nio.file.Files ;
import java.security.KeyPair ;
import java.security.PublicKey ;
import java.util.ArrayList ;
@ -78,70 +82,67 @@ import java.util.Comparator;
import java.util.HashSet ;
import java.util.List ;
import java.util.Set ;
import java.util.UUID ;
import java.util.function.Function ;
import java.util.stream.Collectors ;
public class GitRepository {
private final Git git ;
private final AuthHandler authHandler ;
@Getter
private final RepositorySettings settings ;
private final CredentialsProvider credentialsProvider ;
private final SshdSessionFactory sshSessionFactory ;
@Getter
private final String directory ;
private ObjectId headId ;
private GitRepository ( Git git , RepositorySettings settings , CredentialsProvider credentialsProvider , SshdSessionFactory sshSessionFactory , String directory ) {
private GitRepository ( Git git , RepositorySettings settings , AuthHandler authHandler , String directory ) {
this . git = git ;
this . settings = settings ;
this . credentialsProvider = credentialsProvider ;
this . sshSessionFactory = sshSessionFactory ;
this . authHandler = authHandler ;
this . directory = directory ;
}
public static GitRepository clone ( RepositorySettings settings , File directory ) throws GitAPIException {
CredentialsProvider credentialsProvider = null ;
SshdSessionFactory sshSessionFactory = null ;
if ( RepositoryAuthMethod . USERNAME_PASSWORD . equals ( settings . getAuthMethod ( ) ) ) {
credentialsProvider = newCredentialsProvider ( settings . getUsername ( ) , settings . getPassword ( ) ) ;
} else if ( RepositoryAuthMethod . PRIVATE_KEY . equals ( settings . getAuthMethod ( ) ) ) {
sshSessionFactory = newSshdSessionFactory ( settings . getPrivateKey ( ) , settings . getPrivateKeyPassword ( ) , directory ) ;
}
CloneCommand cloneCommand = Git . cloneRepository ( )
. setURI ( settings . getRepositoryUri ( ) )
. setDirectory ( directory )
. setNoCheckout ( true ) ;
configureTransportCommand ( cloneCommand , credentialsProvider , sshSessionFactory ) ;
AuthHandler authHandler = AuthHandler . createFor ( settings , directory ) ;
authHandler . configureCommand ( cloneCommand ) ;
Git git = cloneCommand . call ( ) ;
return new GitRepository ( git , settings , credentialsProvider , sshSessionFactory , directory . getAbsolutePath ( ) ) ;
return new GitRepository ( git , settings , authHandler , directory . getAbsolutePath ( ) ) ;
}
public static GitRepository open ( File directory , RepositorySettings settings ) throws IOException {
Git git = Git . open ( directory ) ;
CredentialsProvider credentialsProvider = null ;
SshdSessionFactory sshSessionFactory = null ;
if ( RepositoryAuthMethod . USERNAME_PASSWORD . equals ( settings . getAuthMethod ( ) ) ) {
credentialsProvider = newCredentialsProvider ( settings . getUsername ( ) , settings . getPassword ( ) ) ;
} else if ( RepositoryAuthMethod . PRIVATE_KEY . equals ( settings . getAuthMethod ( ) ) ) {
sshSessionFactory = newSshdSessionFactory ( settings . getPrivateKey ( ) , settings . getPrivateKeyPassword ( ) , directory ) ;
}
return new GitRepository ( git , settings , credentialsProvider , sshSessionFactory , directory . getAbsolutePath ( ) ) ;
AuthHandler authHandler = AuthHandler . createFor ( settings , directory ) ;
return new GitRepository ( git , settings , authHandler , directory . getAbsolutePath ( ) ) ;
}
public static void test ( RepositorySettings settings , File directory ) throws GitAPIException {
CredentialsProvider credentialsProvider = null ;
SshdSessionFactory sshSessionFactory = null ;
if ( RepositoryAuthMethod . USERNAME_PASSWORD . equals ( settings . getAuthMethod ( ) ) ) {
credentialsProvider = newCredentialsProvider ( settings . getUsername ( ) , settings . getPassword ( ) ) ;
} else if ( RepositoryAuthMethod . PRIVATE_KEY . equals ( settings . getAuthMethod ( ) ) ) {
sshSessionFactory = newSshdSessionFactory ( settings . getPrivateKey ( ) , settings . getPrivateKeyPassword ( ) , directory ) ;
public static void test ( RepositorySettings settings , File directory ) throws Exception {
AuthHandler authHandler = AuthHandler . createFor ( settings , directory ) ;
if ( settings . isReadOnly ( ) ) {
LsRemoteCommand lsRemoteCommand = Git . lsRemoteRepository ( ) . setRemote ( settings . getRepositoryUri ( ) ) ;
authHandler . configureCommand ( lsRemoteCommand ) ;
lsRemoteCommand . call ( ) ;
} else {
Files . createDirectories ( directory . toPath ( ) ) ;
try {
Git git = Git . init ( ) . setDirectory ( directory ) . call ( ) ;
GitRepository repository = new GitRepository ( git , settings , authHandler , directory . getAbsolutePath ( ) ) ;
repository . execute ( repository . git . remoteAdd ( )
. setName ( "origin" )
. setUri ( new URIish ( settings . getRepositoryUri ( ) ) ) ) ;
repository . push ( "" , UUID . randomUUID ( ) . toString ( ) ) ; // trying to delete non-existing branch on remote repo
} finally {
try {
FileUtils . forceDelete ( directory ) ;
} catch ( Exception ignored ) { }
}
}
LsRemoteCommand lsRemoteCommand = Git . lsRemoteRepository ( ) . setRemote ( settings . getRepositoryUri ( ) ) ;
configureTransportCommand ( lsRemoteCommand , credentialsProvider , sshSessionFactory ) ;
lsRemoteCommand . call ( ) ;
}
public void fetch ( ) throws GitAPIException {
@ -363,12 +364,12 @@ public class GitRepository {
private < C extends GitCommand < T > , T > T execute ( C command ) throws GitAPIException {
if ( command instanceof TransportCommand ) {
configureTransportCommand ( ( TransportCommand ) command , credentialsProvider , sshSessionFactory ) ;
authHandler . configureCommand ( ( TransportCommand ) command ) ;
}
return command . call ( ) ;
}
private static Function < PageLink , Comparator < RevCommit > > revCommitComparatorFunction = pageLink - > {
private static final Function < PageLink , Comparator < RevCommit > > revCommitComparatorFunction = pageLink - > {
SortOrder sortOrder = pageLink . getSortOrder ( ) ;
if ( sortOrder ! = null
& & sortOrder . getProperty ( ) . equals ( "timestamp" )
@ -405,59 +406,76 @@ public class GitRepository {
return new PageData < > ( data , totalPages , totalElements , hasNext ) ;
}
private static void configureTransportCommand ( TransportCommand transportCommand , CredentialsProvider credentialsProvider , SshdSessionFactory sshSessionFactory ) {
if ( credentialsProvider ! = null ) {
transportCommand . setCredentialsProvider ( credentialsProvider ) ;
}
if ( sshSessionFactory ! = null ) {
transportCommand . setTransportConfigCallback ( transport - > {
if ( transport instanceof SshTransport ) {
SshTransport sshTransport = ( SshTransport ) transport ;
sshTransport . setSshSessionFactory ( sshSessionFactory ) ;
}
} ) ;
@RequiredArgsConstructor
private static class AuthHandler {
private final CredentialsProvider credentialsProvider ;
private final SshdSessionFactory sshSessionFactory ;
protected static AuthHandler createFor ( RepositorySettings settings , File directory ) {
CredentialsProvider credentialsProvider = null ;
SshdSessionFactory sshSessionFactory = null ;
if ( RepositoryAuthMethod . USERNAME_PASSWORD . equals ( settings . getAuthMethod ( ) ) ) {
credentialsProvider = newCredentialsProvider ( settings . getUsername ( ) , settings . getPassword ( ) ) ;
} else if ( RepositoryAuthMethod . PRIVATE_KEY . equals ( settings . getAuthMethod ( ) ) ) {
sshSessionFactory = newSshdSessionFactory ( settings . getPrivateKey ( ) , settings . getPrivateKeyPassword ( ) , directory ) ;
}
return new AuthHandler ( credentialsProvider , sshSessionFactory ) ;
}
}
private static CredentialsProvider newCredentialsProvider ( String username , String password ) {
return new UsernamePasswordCredentialsProvider ( username , password = = null ? "" : password ) ;
}
protected void configureCommand ( TransportCommand command ) {
if ( credentialsProvider ! = null ) {
command . setCredentialsProvider ( credentialsProvider ) ;
}
if ( sshSessionFactory ! = null ) {
command . setTransportConfigCallback ( transport - > {
if ( transport instanceof SshTransport ) {
SshTransport sshTransport = ( SshTransport ) transport ;
sshTransport . setSshSessionFactory ( sshSessionFactory ) ;
}
} ) ;
}
}
private static SshdSessionFactory newSshdSessionFactory ( String privateKey , String password , File directory ) {
SshdSessionFactory sshSessionFactory = null ;
if ( StringUtils . isNotBlank ( privateKey ) ) {
Iterable < KeyPair > keyPairs = loadKeyPairs ( privateKey , password ) ;
sshSessionFactory = new SshdSessionFactoryBuilder ( )
. setPreferredAuthentications ( "publickey" )
. setDefaultKeysProvider ( file - > keyPairs )
. setHomeDirectory ( directory )
. setSshDirectory ( directory )
. setServerKeyDatabase ( ( file , file2 ) - > new ServerKeyDatabase ( ) {
@Override
public List < PublicKey > lookup ( String connectAddress , InetSocketAddress remoteAddress , Configuration config ) {
return Collections . emptyList ( ) ;
}
private static CredentialsProvider newCredentialsProvider ( String username , String password ) {
return new UsernamePasswordCredentialsProvider ( username , password = = null ? "" : password ) ;
}
@Override
public boolean accept ( String connectAddress , InetSocketAddress remoteAddress , PublicKey serverKey , Configuration config , CredentialsProvider provider ) {
return true ;
}
} )
. build ( new JGitKeyCache ( ) ) ;
private static SshdSessionFactory newSshdSessionFactory ( String privateKey , String password , File directory ) {
SshdSessionFactory sshSessionFactory = null ;
if ( StringUtils . isNotBlank ( privateKey ) ) {
Iterable < KeyPair > keyPairs = loadKeyPairs ( privateKey , password ) ;
sshSessionFactory = new SshdSessionFactoryBuilder ( )
. setPreferredAuthentications ( "publickey" )
. setDefaultKeysProvider ( file - > keyPairs )
. setHomeDirectory ( directory )
. setSshDirectory ( directory )
. setServerKeyDatabase ( ( file , file2 ) - > new ServerKeyDatabase ( ) {
@Override
public List < PublicKey > lookup ( String connectAddress , InetSocketAddress remoteAddress , Configuration config ) {
return Collections . emptyList ( ) ;
}
@Override
public boolean accept ( String connectAddress , InetSocketAddress remoteAddress , PublicKey serverKey , Configuration config , CredentialsProvider provider ) {
return true ;
}
} )
. build ( new JGitKeyCache ( ) ) ;
}
return sshSessionFactory ;
}
return sshSessionFactory ;
}
private static Iterable < KeyPair > loadKeyPairs ( String privateKeyContent , String password ) {
Iterable < KeyPair > keyPairs = null ;
try {
keyPairs = SecurityUtils . loadKeyPairIdentities ( null ,
null , new ByteArrayInputStream ( privateKeyContent . getBytes ( ) ) , ( session , resourceKey , retryIndex ) - > password ) ;
} catch ( Exception e ) { }
if ( keyPairs = = null ) {
throw new IllegalArgumentException ( "Failed to load ssh private key" ) ;
private static Iterable < KeyPair > loadKeyPairs ( String privateKeyContent , String password ) {
Iterable < KeyPair > keyPairs = null ;
try {
keyPairs = SecurityUtils . loadKeyPairIdentities ( null ,
null , new ByteArrayInputStream ( privateKeyContent . getBytes ( ) ) , ( session , resourceKey , retryIndex ) - > password ) ;
} catch ( Exception e ) { }
if ( keyPairs = = null ) {
throw new IllegalArgumentException ( "Failed to load ssh private key" ) ;
}
return keyPairs ;
}
return keyPairs ;
}
private static class NoMergesAndCommitMessageFilter extends RevFilter {