have a good time
parent
6a908b2adc
commit
acc2f12acc
|
@ -0,0 +1,16 @@
|
|||
FROM golang:1.13-alpine3.10 AS build-env
|
||||
|
||||
#Setup
|
||||
COPY . /src/gitea-group-sync
|
||||
WORKDIR /src/gitea-group-sync
|
||||
|
||||
#Build deps
|
||||
RUN apk --no-cache add build-base git
|
||||
|
||||
RUN go get gopkg.in/ldap.v3 && go get gopkg.in/robfig/cron.v3 && go build
|
||||
|
||||
FROM alpine:3.10
|
||||
|
||||
COPY --from=build-env /src/gitea-group-sync/gitea-group-sync /app/gitea-group-sync/gitea-group-sync
|
||||
RUN ln -s /app/gitea-group-sync/gitea-group-sync /usr/local/bin/gitea-group-sync
|
||||
ENTRYPOINT ["/usr/local/bin/gitea-group-sync"]
|
|
@ -0,0 +1,16 @@
|
|||
version: '3'
|
||||
services:
|
||||
group-sync:
|
||||
container_name: gitea-group-sync
|
||||
build: .
|
||||
image: localhost:5000/gitea-group-sync
|
||||
environment:
|
||||
GITEA_TOKEN: c00c810bb668c63ce7cd8057411d2f560eac469c
|
||||
GITEA_URL: http://192.168.2.2:3000
|
||||
LDAP_URL: 192.168.2.2
|
||||
LDAP_TLS_PORT: 636
|
||||
BIND_DN: cn=admin,dc=planetexpress,dc=com
|
||||
BIND_PASSWORD: GoodNewsEveryone
|
||||
LDAP_FILTER: (&(objectClass=person)(memberOf=cn=%s,ou=people,dc=planetexpress,dc=com))
|
||||
LDAP_USER_SEARCH_BASE: 'ou=people,dc=planetexpress,dc=com'
|
||||
REP_TIME: '@every 1m'
|
|
@ -0,0 +1,234 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
import "gopkg.in/ldap.v3"
|
||||
import "gopkg.in/robfig/cron.v3"
|
||||
|
||||
func AddUsersToTeam(apiKeys GiteaKeys, users []Account, team int) bool {
|
||||
|
||||
for i := 0; i < len(users); i++ {
|
||||
|
||||
fullusername := url.PathEscape(fmt.Sprintf("%s", users[i].Full_name))
|
||||
apiKeys.Command = "/api/v1/users/search?q=" + fullusername + "&access_token="
|
||||
foundUsers := RequestSearchResults(apiKeys)
|
||||
|
||||
for j := 0; j < len(foundUsers.Data); j++ {
|
||||
|
||||
if strings.EqualFold(users[i].Login, foundUsers.Data[j].Login) {
|
||||
apiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", team) + "/members/" + foundUsers.Data[j].Login + "?access_token="
|
||||
error := RequestPut(apiKeys)
|
||||
if len(error) > 0 {
|
||||
log.Println("Error (Team does not exist or Not Found User) :", parseJson(error).(map[string]interface{})["message"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func DelUsersFromTeam(apiKeys GiteaKeys, Users []Account, team int) bool {
|
||||
|
||||
for i := 0; i < len(Users); i++ {
|
||||
|
||||
apiKeys.Command = "/api/v1/users/search?uid=" + fmt.Sprintf("%d", Users[i].Id) + "&access_token="
|
||||
|
||||
foundUser := RequestSearchResults(apiKeys)
|
||||
|
||||
apiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", team) + "/members/" + foundUser.Data[0].Login + "?access_token="
|
||||
RequestDel(apiKeys)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
mainJob() // First run for check settings
|
||||
|
||||
var repTime string
|
||||
if len(os.Getenv("REP_TIME")) == 0 {
|
||||
|
||||
} else {
|
||||
repTime = os.Getenv("REP_TIME")
|
||||
}
|
||||
|
||||
c := cron.New()
|
||||
c.AddFunc(repTime, mainJob)
|
||||
c.Start()
|
||||
fmt.Println(c.Entries())
|
||||
for true {
|
||||
time.Sleep(100*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func mainJob() {
|
||||
|
||||
//------------------------------
|
||||
// Check and Set input settings
|
||||
//------------------------------
|
||||
|
||||
var apiKeys GiteaKeys
|
||||
|
||||
if len(os.Getenv("GITEA_TOKEN")) < 40 { // get on https://[web_site_url]/user/settings/applications
|
||||
log.Println("GITEA_TOKEN is empty or invalid.")
|
||||
} else {
|
||||
apiKeys.TokenKey = strings.Split(os.Getenv("GITEA_TOKEN"), ",")
|
||||
}
|
||||
|
||||
if len(os.Getenv("GITEA_URL")) == 0 {
|
||||
log.Println("GITEA_URL is empty")
|
||||
} else {
|
||||
apiKeys.BaseUrl = os.Getenv("GITEA_URL")
|
||||
}
|
||||
|
||||
var ldapUrl string = "ucs.totalwebservices.net"
|
||||
if len(os.Getenv("LDAP_URL")) == 0 {
|
||||
log.Println("LDAP_URL is empty")
|
||||
} else {
|
||||
ldapUrl = os.Getenv("LDAP_URL")
|
||||
}
|
||||
|
||||
var ldapPort int
|
||||
if len(os.Getenv("LDAP_TLS_PORT")) > 0 {
|
||||
port, err := strconv.Atoi(os.Getenv("LDAP_TLS_PORT"))
|
||||
ldapPort = port
|
||||
log.Printf("DialTLS:=%v:%d", ldapUrl, ldapPort)
|
||||
if err != nil {
|
||||
log.Println("LDAP_TLS_PORT is invalid.")
|
||||
}
|
||||
} else {
|
||||
log.Println("LDAP_TLS_PORT is empty")
|
||||
}
|
||||
|
||||
var ldapbindDN string
|
||||
if len(os.Getenv("BIND_DN")) == 0 {
|
||||
log.Println("BIND_DN is empty")
|
||||
} else {
|
||||
ldapbindDN = os.Getenv("BIND_DN")
|
||||
}
|
||||
|
||||
var ldapbindPassword string
|
||||
if len(os.Getenv("BIND_PASSWORD")) == 0 {
|
||||
log.Println("BIND_PASSWORD is empty")
|
||||
} else {
|
||||
ldapbindPassword = os.Getenv("BIND_PASSWORD")
|
||||
}
|
||||
|
||||
var ldapUserFilter string
|
||||
if len(os.Getenv("LDAP_FILTER")) == 0 {
|
||||
log.Println("LDAP_FILTER is empty")
|
||||
} else {
|
||||
ldapUserFilter = os.Getenv("LDAP_FILTER")
|
||||
}
|
||||
|
||||
var ldapUserSearchBase string
|
||||
if len(os.Getenv("LDAP_USER_SEARCH_BASE")) == 0 {
|
||||
log.Println("LDAP_USER_SEARCH_BASE is empty")
|
||||
} else {
|
||||
ldapUserSearchBase = os.Getenv("LDAP_USER_SEARCH_BASE")
|
||||
}
|
||||
|
||||
l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapUrl, ldapPort), &tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Please set the correct values for all specifics.")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
err = l.Bind(ldapbindDN, ldapbindPassword)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
page := 1
|
||||
apiKeys.BruteforceTokenKey = 0
|
||||
apiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations
|
||||
organizationList := RequestOrganizationList(apiKeys)
|
||||
|
||||
log.Printf("%d organizations were found on the server: %s", len(organizationList), apiKeys.BaseUrl)
|
||||
|
||||
for 1 < len(organizationList) {
|
||||
|
||||
for i := 0; i < len(organizationList); i++ {
|
||||
|
||||
log.Println(organizationList)
|
||||
|
||||
log.Printf("Begin an organization review: OrganizationName= %v, OrganizationId= %d \n", organizationList[i].Name, organizationList[i].Id)
|
||||
|
||||
apiKeys.Command = "/api/v1/orgs/" + organizationList[i].Name + "/teams?access_token="
|
||||
teamList := RequestTeamList(apiKeys)
|
||||
log.Printf("%d teams were found in %s organization", len(teamList), organizationList[i].Name)
|
||||
log.Printf("Skip synchronization in the Owners team")
|
||||
apiKeys.BruteforceTokenKey = 0
|
||||
|
||||
for j := 1; j < len(teamList); j++ {
|
||||
|
||||
// preparing request to ldap server
|
||||
filter := fmt.Sprintf(ldapUserFilter, teamList[j].Name)
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
ldapUserSearchBase, // The base dn to search
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
filter, // The filter to apply
|
||||
[]string{"cn", "uid", "mailPrimaryAddress, sn"}, // A list attributes to retrieve
|
||||
nil,
|
||||
)
|
||||
// make request to ldap server
|
||||
sr, err := l.Search(searchRequest)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
AccountsLdap := make(map[string]Account)
|
||||
AccountsGitea := make(map[string]Account)
|
||||
var addUserToTeamList, delUserToTeamlist []Account
|
||||
if len(sr.Entries) > 0 {
|
||||
log.Printf("The LDAP %s has %d users corresponding to team %s", ldapUrl, len(sr.Entries), teamList[j].Name)
|
||||
for _, entry := range sr.Entries {
|
||||
|
||||
AccountsLdap[entry.GetAttributeValue("uid")] = Account{
|
||||
Full_name: entry.GetAttributeValue("sn"), //change to cn if you need it
|
||||
Login: entry.GetAttributeValue("uid"),
|
||||
}
|
||||
}
|
||||
|
||||
apiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", teamList[j].Id) + "/members?access_token="
|
||||
AccountsGitea, apiKeys.BruteforceTokenKey = RequestUsersList(apiKeys)
|
||||
log.Printf("The gitea %s has %d users corresponding to team %s Teamid=%d", apiKeys.BaseUrl, len(AccountsGitea), teamList[j].Name, teamList[j].Id)
|
||||
|
||||
for k, v := range AccountsLdap {
|
||||
if AccountsGitea[k].Login != v.Login {
|
||||
addUserToTeamList = append(addUserToTeamList, v)
|
||||
}
|
||||
}
|
||||
log.Printf("can be added users list %v", addUserToTeamList)
|
||||
AddUsersToTeam(apiKeys, addUserToTeamList, teamList[j].Id)
|
||||
|
||||
for k, v := range AccountsGitea {
|
||||
if AccountsLdap[k].Login != v.Login {
|
||||
delUserToTeamlist = append(delUserToTeamlist, v)
|
||||
}
|
||||
}
|
||||
log.Printf("must be del users list %v", delUserToTeamlist)
|
||||
DelUsersFromTeam(apiKeys, delUserToTeamlist, teamList[j].Id)
|
||||
|
||||
} else {
|
||||
log.Printf("The LDAP %s not found users corresponding to team %s", ldapUrl, teamList[j].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
page++
|
||||
apiKeys.BruteforceTokenKey = 0
|
||||
apiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations
|
||||
organizationList = RequestOrganizationList(apiKeys)
|
||||
log.Printf("%d organizations were found on the server: %s", len(organizationList), apiKeys.BaseUrl)
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 314 KiB |
|
@ -0,0 +1,196 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
// "reflect"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CheckStatusCode(res *http.Response) {
|
||||
|
||||
switch {
|
||||
case 300 <= res.StatusCode && res.StatusCode < 400:
|
||||
fmt.Println("CheckStatusCode gitea apiKeys connection error: Redirect message")
|
||||
case 401 == res.StatusCode:
|
||||
fmt.Println("CheckStatusCode gitea apiKeys connection Error: Unauthorized")
|
||||
case 400 <= res.StatusCode && res.StatusCode < 500:
|
||||
fmt.Println("CheckStatusCode gitea apiKeys connection error: Client error")
|
||||
case 500 <= res.StatusCode && res.StatusCode < 600:
|
||||
fmt.Println("CheckStatusCode gitea apiKeys connection error Server error")
|
||||
}
|
||||
}
|
||||
|
||||
func hasTimedOut(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case *url.Error:
|
||||
if err, ok := err.Err.(net.Error); ok && err.Timeout() {
|
||||
return true
|
||||
}
|
||||
case net.Error:
|
||||
if err.Timeout() {
|
||||
return true
|
||||
}
|
||||
case *net.OpError:
|
||||
if err.Timeout() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
errTxt := "use of closed network connection"
|
||||
if err != nil && strings.Contains(err.Error(), errTxt) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func RequestGet(apiKeys GiteaKeys) []byte {
|
||||
cc := &http.Client{Timeout: time.Second * 2}
|
||||
url := apiKeys.BaseUrl + apiKeys.Command + apiKeys.TokenKey[apiKeys.BruteforceTokenKey]
|
||||
res, err := cc.Get(url)
|
||||
if err != nil && hasTimedOut(err) {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
CheckStatusCode(res)
|
||||
b, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
res.Body.Close()
|
||||
return b
|
||||
}
|
||||
|
||||
func RequestPut(apiKeys GiteaKeys) []byte {
|
||||
cc := &http.Client{Timeout: time.Second * 2}
|
||||
url := apiKeys.BaseUrl + apiKeys.Command + apiKeys.TokenKey[apiKeys.BruteforceTokenKey]
|
||||
request, err := http.NewRequest("PUT", url, nil)
|
||||
res, err := cc.Do(request)
|
||||
CheckStatusCode(res)
|
||||
if err != nil && hasTimedOut(err) {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
b, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
res.Body.Close()
|
||||
return b
|
||||
}
|
||||
|
||||
func RequestDel(apiKeys GiteaKeys) []byte {
|
||||
|
||||
cc := &http.Client{Timeout: time.Second * 2}
|
||||
url := apiKeys.BaseUrl + apiKeys.Command + apiKeys.TokenKey[apiKeys.BruteforceTokenKey]
|
||||
request, err := http.NewRequest("DELETE", url, nil)
|
||||
res, err := cc.Do(request)
|
||||
CheckStatusCode(res)
|
||||
if err != nil && hasTimedOut(err) {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
b, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
res.Body.Close()
|
||||
return b
|
||||
}
|
||||
|
||||
func RequestSearchResults(ApiKeys GiteaKeys) SearchResults {
|
||||
|
||||
b := RequestGet(ApiKeys)
|
||||
|
||||
var f SearchResults
|
||||
jsonErr := json.Unmarshal(b, &f)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func RequestUsersList(ApiKeys GiteaKeys) (map[string]Account, int) {
|
||||
|
||||
b := RequestGet(ApiKeys)
|
||||
var Account_u = make(map[string]Account)
|
||||
|
||||
var f []Account
|
||||
jsonErr := json.Unmarshal(b, &f)
|
||||
if jsonErr != nil {
|
||||
log.Println(jsonErr)
|
||||
if ApiKeys.BruteforceTokenKey == len(ApiKeys.TokenKey)-1 {
|
||||
log.Println("Token key is unsuitable, call to system administrator ")
|
||||
} else {
|
||||
log.Println("Can't get UsersList try another token key")
|
||||
}
|
||||
if ApiKeys.BruteforceTokenKey < len(ApiKeys.TokenKey)-1 {
|
||||
ApiKeys.BruteforceTokenKey++
|
||||
log.Printf("BruteforceTokenKey=%d", ApiKeys.BruteforceTokenKey)
|
||||
Account_u, ApiKeys.BruteforceTokenKey = RequestUsersList(ApiKeys)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(f); i++ {
|
||||
Account_u[f[i].Login] = Account{
|
||||
// Email: f[i].Email,
|
||||
Id: f[i].Id,
|
||||
Full_name: f[i].Full_name,
|
||||
Login: f[i].Login,
|
||||
}
|
||||
}
|
||||
return Account_u, ApiKeys.BruteforceTokenKey
|
||||
}
|
||||
|
||||
func RequestOrganizationList(apiKeys GiteaKeys) []Organization {
|
||||
|
||||
b := RequestGet(apiKeys)
|
||||
|
||||
var f []Organization
|
||||
jsonErr := json.Unmarshal(b, &f)
|
||||
if jsonErr != nil {
|
||||
log.Printf("Please check setting GITEA_TOKEN, GITEA_URL ")
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func RequestTeamList(apiKeys GiteaKeys) []Team {
|
||||
|
||||
b := RequestGet(apiKeys)
|
||||
|
||||
var f []Team
|
||||
jsonErr := json.Unmarshal(b, &f)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func parseJson(b []byte) interface{} {
|
||||
var f interface{}
|
||||
jsonErr := json.Unmarshal(b, &f)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
m := f.(interface{})
|
||||
return m
|
||||
}
|
||||
|
||||
func parseJsonArray(b []byte) []interface{} {
|
||||
var f interface{}
|
||||
jsonErr := json.Unmarshal(b, &f)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
m := f.([]interface{})
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
export GITEA_TOKEN=c00c810bb668c63ce7cd8057411d2f560eac469c,2c02df6959d012dee8f5da3539f63223417c4bbe
|
||||
export GITEA_URL=http://localhost:3000
|
||||
export LDAP_URL=localhost
|
||||
export LDAP_TLS_PORT=636
|
||||
export BIND_DN='cn=admin,dc=planetexpress,dc=com'
|
||||
export BIND_PASSWORD=GoodNewsEveryone
|
||||
export LDAP_FILTER='(&(objectClass=person)(memberOf=cn=%s,ou=people,dc=planetexpress,dc=com))'
|
||||
export LDAP_USER_SEARCH_BASE='ou=people,dc=planetexpress,dc=com'
|
||||
export REP_TIME='@every 1m'
|
||||
go run .
|
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
type Organization struct {
|
||||
Id int `json:"id"`
|
||||
Avatar_url string `json:"avatar_url"`
|
||||
Description string `json:"description"`
|
||||
Full_name string `json:"full_name"`
|
||||
Location string `json:"location"`
|
||||
Name string `json:"username"`
|
||||
Visibility string `json:"visibility"`
|
||||
Website string `json:"website"`
|
||||
}
|
||||
|
||||
type Team struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
type User struct {
|
||||
Id int `json:"id"`
|
||||
Avatar_url string `json:"avatar_url"`
|
||||
Created string `json:"created"`
|
||||
Email string `json:"email"`
|
||||
Full_name string `json:"full_name"`
|
||||
Is_admin bool `json:"is_admin"`
|
||||
Language string `json:"language"`
|
||||
Last_login string `json:"last_login"`
|
||||
Login string `json:"login"`
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
Id int `json:"id"`
|
||||
Full_name string `json:"full_name"`
|
||||
Login string `json:"login"`
|
||||
}
|
||||
|
||||
type SearchResults struct {
|
||||
Data []User `json:"data"`
|
||||
Ok bool `json:"ok"`
|
||||
}
|
||||
|
||||
type GiteaKeys struct {
|
||||
TokenKey []string
|
||||
BaseUrl string
|
||||
Command string
|
||||
BruteforceTokenKey int
|
||||
}
|
Loading…
Reference in New Issue