I'm using go/ldap to query my active directory to get all the groups of a specific user, the function is working but is not returning the Primary Groups, like Domain Users.
Code example
package main
import (
"encoding/json"
"errors"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/techoner/gophp"
"handlers"
"log"
"reflect"
"strconv"
"strings"
)
func main(){
conn, err := connect(bindServer,BindPort)
if err != nil {
log.Printf("Failed to connect to AD/LDAP with error: %s", err)
return nil, fmt.Errorf("Failed to connect to AD/LDAP with error: %s", err)
}
errBind := conn.Bind(bindUser, bindPWD)
if errBind != nil {
if isLdapDebug {
log.Printf("Failed to bind to AD/LDAP with error: %s", errBind)
}
return nil, fmt.Errorf("Failed to bind to AD/LDAP with error: %s", errBind)
}
searchRequest := ldap.NewSearchRequest(
DC=domain,DC=local,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=user)(sAMAccountName=%s))", administrator),
[]string{"dn"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return nil, err
}
if len(sr.Entries) != 1 {
return nil, errors.New("User does not exist")
}
userdn := sr.Entries[0].DN
log.Printf("USER DN IS =%s", userdn)
searchRequest = ldap.NewSearchRequest(
DC=domain,DC=local,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=group)(member=CN=Administrator,CN=Users,DC=domain,DC=local))"),
[]string{"cn"}, // can it be something else than "cn"?
nil,
)
sr, err = conn.Search(searchRequest)
if err != nil {
return nil, err
}
groups := []string{}
for _, entry := range sr.Entries {
//fmt.Printf("%s", entry)
groups = append(groups, entry.GetAttributeValue("cn"))
}
return groups, nil
}
Output
[Administrators Schema Admins Enterprise Admins Domain Admins Group Policy Creator Owners gteste1 gtest2]
The groups are correcly returned but is missing the primary groups.
Any way to return all groups of a specific user including Primary Groups?
The primary group is different. You have to look at the primaryGroupId attribute on the user, then search for the group that has that value in its primaryGroupToken attribute.
In most cases, the primaryGroupId will be 513, which corresponds to the Domain Users group.
A little more detail in an article I wrote on this: Active Directory: What makes a member a member?
To find the primary group in go using the lib
github.com/go-ldap/ldap/v3
I had to use this code sample:
userdn := sr.Entries[0].DN
groups := []string{}
searchRequest = ldap.NewSearchRequest(
data.Record.LdapSuffix,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, SearchTimelimit, false,
fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513)(sAMAccountName=%s))", ldap.EscapeFilter(username)),
[]string{"primaryGroupID"},
nil,
)
sr, err = conn.Search(searchRequest)
if err != nil {
continue
}
if len(sr.Entries) > 0 {
primaryGroup := sr.Entries[0].GetAttributeValue("primaryGroupID")
if primaryGroup == "513" {
if searchGroup == "Domain Users" {
return true
}
}
}
Related
I have the following code that works perfectly when binding to an LDAP server without TLS/SSL but when I try to bind to a LDAP server that has TLS setup, it doesn't bind.
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "ldaps://test.com:636"
ldapBind = "CN=ad_binder,CN=Users,DC=dev,DC=test,DC=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=dev,dc=test,dc=com"
loginUsername = "ad_binder"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
Here is the error I get when I try to run it:
Failed to connect. Failed to connect. LDAP Result Code 200 "Network Error": dial tcp: address ldaps://test.com:636: too many colons in address
Which is weird since it works with a different LDAP server just fine but with the following:
ldapServer = "10.1.30.47:389"
instead of
ldapServer = "ldaps://test.com:636"
So any help would be greatly appreciated, thanks!
gopkg.in/ldap.v2 does not support URI addressing (i.e. you cannot put the scheme ldap:// or ldaps:// before the network address).
Note: gopkg.in/ldap.v3 does support URI dialing (ldaps://test.com) via DialURL.
If you are using gopkg.in/ldap.v2, you can still establish a direct TLS connection, but you must use the function DialTLS, and use a network address (not a URI):
// addr = "10.1.30.47:389"
// tlsAddr = "10.1.30.47:636"
// conn, err = ldap.Dial("tcp", addr) // non-TLS
// serverName = "test.com" TLS cert name will be verified
// serverName = "" TLS verify will be disabled (DON'T DO THIS IN PRODUCTION)
tlsConf, err := getTLSconfig(serverName)
if err != nil { /* */ }
conn, err = ldap.DialTLS("tcp", tlsAddr, tlsConf)
the above needs a *tls.Config, so use a helper function like:
func getTLSconfig(tlsName string) (tlsC *tls.Config, err error) {
if tlsName != "" {
tlsC = &tls.Config{
ServerName: tlsName,
}
return
}
log.Println("No TLS verification enabled! ***STRONGLY*** recommend adding a trust file to the config.")
tlsC = &tls.Config{
InsecureSkipVerify: true,
}
return
}
I'm using the gopkg.in/ldap.v2 api to query an LDAP server, but was wondering how to retrieve all the attributes of an entry once it shows up in the query result? Here is the full program I have:
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "127.0.0.1:389"
ldapBind = "cn=admin,dc=test123,dc=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=test123,dc=com"
loginUsername = "admin"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
/*
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}*/
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
The issue I have is in this line:
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"}
I would want to retrieve all the attributes of an LDAP entry rather than having to manually type all the attributes that I want from the query result. Another reason is because I don't know what attributes an entry may have.
Any help would be greatly appreciated. Thanks!
In the LDAP search operation, if you don't specify attributes for the search it will return the entries with all their attributes, so this will do the job:
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
[]string{},
nil,
))
I have the following example:
type PostModel struct {
Id int64 `gorm:"primary_key;AUTO_INCREMENT;column:id"`
UserId *int64 `gorm:"column:userId;"`
User UserModel `gorm:"ForeignKey:Id;AssociationForeignKey:UserId"`
}
type UserModel struct {
Id int64 `gorm:"primary_key;AUTO_INCREMENT;column:id"`
}
when I use
db.Model(&model.PostModel{}).Where(where).Preload("User").Find(&post)
I got a Correct reply, but it has something wrong.
SELECT * FROM `user` WHERE (`id` IN (?,?,?,?,?,?,?,?,?,?))[0xc00023dce0 0xc00023dda0 0xc00023d9e0 0xc00023d920 0xc00023daa0 0xc00023db60 0xc00023dc20 0xc00023de60 0xc00023df20 0xc00023d790] 1
All userid are 1, but in gorm sql,it use pointer address to replace number. like 0xc00023dce0 0xc00023dda0 0xc00023d9e0 0xc00023d920 0xc00023daa0 0xc00023db60 0xc00023dc20 0xc00023de60 0xc00023df20 0xc00023d790.
How to fix it?
I wrote a sample code as you described and put Gorm in log mode to see the SQLs but it prints correct SQLs:
[2020-03-29 21:29:42] [0.17ms] SELECT * FROM "post_models"
[4 rows affected or returned ]
[2020-03-29 21:29:42] [0.18ms] SELECT * FROM "user_models" WHERE ("id" IN (1,2,1,3))
[3 rows affected or returned ]
As you see queries are correct.
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"log"
"os"
)
type PostModel struct {
Id int64 `gorm:"primary_key;column:id"`
UserId *int64 `gorm:"column:userId;"`
User UserModel `gorm:"ForeignKey:UserId"`
}
type UserModel struct {
Id int64 `gorm:"primary_key;column:id"`
}
func preloadingSample() error {
_ = os.Remove("test.db") // Remove file to make sure DB is empty
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
return fmt.Errorf("open DB failed: %w", err)
}
defer db.Close()
db.LogMode(true)
err = db.AutoMigrate(
&PostModel{},
&UserModel{},
).Error
if err != nil {
return fmt.Errorf("migration failed: %w", err)
}
// Put some sample data in DB
sampleUsers := []UserModel{
{},
{},
{},
}
for idx := range sampleUsers {
err = db.Create(&sampleUsers[idx]).Error
if err != nil {
return fmt.Errorf("failed to create: %w", err)
}
}
samplePosts := []PostModel{
{User: sampleUsers[0]},
{User: sampleUsers[1]},
{User: sampleUsers[0]},
{User: sampleUsers[2]},
}
for idx := range samplePosts {
err = db.Create(&samplePosts[idx]).Error
if err != nil {
return fmt.Errorf("failed to create: %w", err)
}
}
var posts []PostModel
err = db.Preload("User").Find(&posts).Error
if err != nil {
return fmt.Errorf("error in find: %w", err)
}
fmt.Printf("%+v\n", posts)
return nil
}
func main() {
err := preloadingSample()
if err != nil {
log.Fatal(err)
}
}
I'm trying to familiarize with Goland and i'd like to make my first bot Discord with it.
I'm making a discord bot and i'm trying to make a weather command using the wego package that's use the forecast's API, here's the package and a screenshot of what it looks like in the readme: here
Here's my code :
package main
import (
"flag"
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/schachmat/ingo"
_ "github.com/schachmat/wego/backends"
_ "github.com/schachmat/wego/frontends"
"github.com/schachmat/wego/iface"
"log"
"os"
"sort"
"strconv"
"strings"
)
var (
commandPrefix string
botID string
)
func pluginLists() {
bEnds := make([]string, 0, len(iface.AllBackends))
for name := range iface.AllBackends {
bEnds = append(bEnds, name)
}
sort.Strings(bEnds)
fEnds := make([]string, 0, len(iface.AllFrontends))
for name := range iface.AllFrontends {
fEnds = append(fEnds, name)
}
sort.Strings(fEnds)
fmt.Fprintln(os.Stderr, "Available backends:", strings.Join(bEnds, ", "))
fmt.Fprintln(os.Stderr, "Available frontends:", strings.Join(fEnds, ", "))
}
func main() {
discord, err := discordgo.New("Bot My_Api_Key")
errCheck("error creating discord session", err)
user, err := discord.User("#me")
errCheck("error retrieving account", err)
botID = user.ID
discord.AddHandler(commandHandler)
discord.AddHandler(func(discord *discordgo.Session, ready *discordgo.Ready) {
err = discord.UpdateStatus(0, "A friendly helpful bot!")
if err != nil {
fmt.Println("Error attempting to set my status")
}
servers := discord.State.Guilds
fmt.Printf("GoBot has started on %d servers", len(servers))
})
err = discord.Open()
errCheck("Error opening connection to Discord", err)
defer discord.Close()
commandPrefix = "!"
<-make(chan struct{})
for _, be := range iface.AllBackends {
be.Setup()
}
for _, fe := range iface.AllFrontends {
fe.Setup()
}
// initialize global flags and default config
location := flag.String("location", "48.839661,2.375300", "`LOCATION` to be queried")
flag.StringVar(location, "l", "48.839661,2.375300", "`LOCATION` to be queried (shorthand)")
numdays := flag.Int("days", 1, "`NUMBER` of days of weather forecast to be displayed")
flag.IntVar(numdays, "d", 1, "`NUMBER` of days of weather forecast to be displayed (shorthand)")
unitSystem := flag.String("units", "metric", "`UNITSYSTEM` to use for output.\n \tChoices are: metric, imperial, si, metric-ms")
flag.StringVar(unitSystem, "u", "metric", "`UNITSYSTEM` to use for output. (shorthand)\n \tChoices are: metric, imperial, si, metric-ms")
selectedBackend := flag.String("backend", "forecast.io", "`BACKEND` to be used")
flag.StringVar(selectedBackend, "b", "forecast.io", "`BACKEND` to be used (shorthand)")
selectedFrontend := flag.String("frontend", "ascii-art-table", "`FRONTEND` to be used")
flag.StringVar(selectedFrontend, "f", "ascii-art-table", "`FRONTEND` to be used (shorthand)")
// print out a list of all backends and frontends in the usage
tmpUsage := flag.Usage
flag.Usage = func() {
tmpUsage()
pluginLists()
}
// read/write config and parse flags
if err := ingo.Parse("wego"); err != nil {
log.Fatalf("Error parsing config: %v", err)
}
// non-flag shortcut arguments overwrite possible flag arguments
for _, arg := range flag.Args() {
if v, err := strconv.Atoi(arg); err == nil && len(arg) == 1 {
*numdays = v
} else {
*location = arg
}
}
// get selected backend and fetch the weather data from it
be, ok := iface.AllBackends[*selectedBackend]
if !ok {
log.Fatalf("Could not find selected backend \"%s\"", *selectedBackend)
}
r := be.Fetch(*location, *numdays)
// set unit system
unit := iface.UnitsMetric
if *unitSystem == "imperial" {
unit = iface.UnitsImperial
} else if *unitSystem == "si" {
unit = iface.UnitsSi
} else if *unitSystem == "metric-ms" {
unit = iface.UnitsMetricMs
}
// get selected frontend and render the weather data with it
fe, ok := iface.AllFrontends[*selectedFrontend]
if !ok {
log.Fatalf("Could not find selected frontend \"%s\"", *selectedFrontend)
}
fe.Render(r, unit)
}
func errCheck(msg string, err error) {
if err != nil {
fmt.Printf("%s: %+v", msg, err)
panic(err)
}
}
func commandHandler(discord *discordgo.Session, message *discordgo.MessageCreate) {
user := message.Author
if user.ID == botID || user.Bot {
//Do nothing because the bot is talking
return
}
if message.Content == "test" {
discord.ChannelMessageSend(message.ChannelID, "test")
}
fmt.Printf("Message: %+v || From: %s\n", message.Message, message.Author)
}
So this does nothing, it just show the message that an user wrote in a channel. I saw that the print of the wego in the terminal is located in src/github.com/schachmat/frontends/emoji.go but i don't know how to use it with my bot, i'd like to print that when an user type "!weather".
I'm lost and don't know what to do.
Sorry for the english, i tried my best, and it's my first post on StackOverflow :)
// datastore1
package main
import (
"fmt"
"io/ioutil"
"log"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud"
"google.golang.org/cloud/datastore"
)
const (
// ScopeDatastore grants permissions to view and/or manage datastore entities
copeDatastore = "https://www.googleapis.com/auth/datastore"
// ScopeUserEmail grants permission to view the user's email address.
// It is required to access the datastore.
ScopeUserEmail = "https://www.googleapis.com/auth/userinfo.email"
)
type ehrEntity struct {
email *datastore.Key
firstname string
lastname string
address string
age int8
dateofbirth time.Time
sex bool
}
func getCtx() *datastore.Client {
// Initialize an authorized transport with Google Developers Console
// JSON key. Read the google package examples to learn more about
// different authorization flows you can use.
// http://godoc.org/golang.org/x/oauth2/google
jsonKey, err := ioutil.ReadFile("filename.json")
opts, err := google.JWTConfigFromJSON(
jsonKey,
datastore.ScopeDatastore,
datastore.ScopeUserEmail,
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client, err := datastore.NewClient(ctx, "xxxx", cloud.WithTokenSource(opts.TokenSource(ctx)))
if err != nil {
log.Fatal(err)
}
// Use the context (see other examples)
return client
}
func ExampleGet() {
ctx := context.Background()
client, err := datastore.NewClient(ctx, "xxxx")
if err != nil {
log.Fatal(err)
}
key := datastore.NewKey(ctx, "User", "tluu#abc.com", 0, nil)
ehr := ehrEntity{
nil,
"tri",
"luu",
"addr1",
20,
time.Date(2009, time.January, 10, 23, 0, 0, 0, time.UTC),
false}
if err := client.Get(ctx, key, ehr); err != nil {
log.Fatal(err)
}
}
func main() {
getCtx()
fmt.Println("Pass authentication")
ExampleGet()
}
When I run go file, It return error follow:
Pass authentication (Pass getCtx() function).
Error in ExampleGet()
May be at
ctx := context.Background()
client, err := datastore.NewClient(ctx, "xxxx")
if err != nil {
log.Fatal(err)
}
Error:
2016/01/09 22:08:43 Post https://www.googleapis.com/datastore/v1beta2/datasets/xxxx/lookup: oauth2: cannot fetch token: 400 Bad Request
Response: {
"error" : "invalid_grant"
}
How to resolve this error?
This appears to have worked for me.
If you are on linux try the following:
1. apt-get update
2. apt-get install ntp
3. /etc/init.d/ntp restart