Selukov Andrey
5 years ago
12 changed files with 521 additions and 0 deletions
@ -0,0 +1,16 @@
@@ -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 @@
@@ -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 @@
@@ -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) |
||||
} |
||||
} |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 314 KiB |
@ -0,0 +1,196 @@
@@ -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 @@
@@ -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 @@
@@ -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