func main() {
	var uuid cassandra.UUID
	var conn *cassandra.RetryCassandraClient
	var r *cassandra.ColumnOrSuperColumn
	var cp *cassandra.ColumnPath
	var err error

	var uuid_str, dbserver, dbname, columnfamily, column string

	flag.StringVar(&uuid_str, "uuid-string", "",
		"UUID string to look at")
	flag.StringVar(&dbserver, "cassandra-server", "localhost:9160",
		"Database server to look at")
	flag.StringVar(&dbname, "dbname", "sfmembersys",
		"Database name to look at")
	flag.StringVar(&columnfamily, "column-family", "",
		"Column family to look at")
	flag.StringVar(&column, "column-name", "",
		"Column name to look at")
	flag.Parse()

	uuid, err = cassandra.ParseUUID(uuid_str)
	if err != nil {
		log.Fatal(err)
	}

	conn, err = cassandra.NewRetryCassandraClient(dbserver)
	if err != nil {
		log.Fatal(err)
	}

	err = conn.SetKeyspace(dbname)
	if err != nil {
		log.Fatal(err)
	}

	cp = cassandra.NewColumnPath()
	cp.ColumnFamily = columnfamily
	cp.Column = []byte(column)

	r, err = conn.Get([]byte(uuid), cp,
		cassandra.ConsistencyLevel_ONE)
	if err != nil {
		log.Fatal(err)
	}

	log.Print(r.Column.Name, ": ", r.Column.Value, " (",
		r.Column.Timestamp, ")")
}
Exemple #2
0
// Create a new connection to the membership database on the given "host".
// Will set the keyspace to "dbname".
func NewMembershipDB(host, dbname string, timeout time.Duration) (*MembershipDB, error) {
	var conn *cassandra.RetryCassandraClient
	var err error
	conn, err = cassandra.NewRetryCassandraClientTimeout(host, timeout)
	if err != nil {
		return nil, err
	}
	err = conn.SetKeyspace(dbname)
	if err != nil {
		return nil, err
	}
	return &MembershipDB{
		conn: conn,
	}, nil
}
func main() {
	var cf string = "members"
	var config_file string
	var config_contents []byte
	var config membersys.MemberCreatorConfig
	var greatestUid uint64 = 1000
	var now time.Time
	var noop, verbose bool
	var welcome *membersys.WelcomeMail

	var ld *ldap.Conn
	var sreq *ldap.SearchRequest
	var lres *ldap.SearchResult
	var entry *ldap.Entry
	var tlsconfig tls.Config

	var mmap map[string]map[string][]*cassandra.Mutation
	var db *cassandra.RetryCassandraClient
	var cp *cassandra.ColumnParent
	var pred *cassandra.SlicePredicate
	var kr *cassandra.KeyRange
	var kss []*cassandra.KeySlice
	var ks *cassandra.KeySlice

	var err error

	flag.StringVar(&config_file, "config", "",
		"Path to the member creator configuration file")
	flag.BoolVar(&noop, "dry-run", false, "Do a dry run")
	flag.BoolVar(&verbose, "verbose", false,
		"Whether or not to display verbose messages")
	flag.Parse()

	if len(config_file) == 0 {
		flag.Usage()
		return
	}

	config_contents, err = ioutil.ReadFile(config_file)
	if err != nil {
		log.Fatal("Unable to read ", config_file, ": ", err)
	}

	err = proto.Unmarshal(config_contents, &config)
	if err != nil {
		err = proto.UnmarshalText(string(config_contents), &config)
	}
	if err != nil {
		log.Fatal("Unable to parse ", config_file, ": ", err)
	}
	if config.WelcomeMailConfig != nil {
		welcome, err = membersys.NewWelcomeMail(
			config.WelcomeMailConfig)
		if err != nil {
			log.Fatal("Error creating WelcomeMail: ", err)
		}
	}

	tlsconfig.MinVersion = tls.VersionTLS12
	tlsconfig.ServerName, _, err = net.SplitHostPort(
		config.LdapConfig.GetServer())
	if err != nil {
		log.Fatal("Can't split ", config.LdapConfig.GetServer(),
			" into host and port: ", err)
	}

	if config.LdapConfig.CaCertificate != nil {
		var certData []byte

		certData, err = ioutil.ReadFile(config.LdapConfig.GetCaCertificate())
		if err != nil {
			log.Fatal("Unable to read certificate from ",
				config.LdapConfig.GetCaCertificate(), ": ", err)
		}

		tlsconfig.RootCAs = x509.NewCertPool()
		tlsconfig.RootCAs.AppendCertsFromPEM(certData)
	}

	now = time.Now()

	if !noop {
		ld, err = ldap.DialTLS("tcp", config.LdapConfig.GetServer(),
			&tlsconfig)
		if err != nil {
			log.Fatal("Error connecting to LDAP server ",
				config.LdapConfig.GetServer(), ": ", err)
		}

		err = ld.Bind(config.LdapConfig.GetSuperUser()+","+
			config.LdapConfig.GetBase(), config.LdapConfig.GetSuperPassword())
		if err != nil {
			log.Fatal("Unable to bind as ", config.LdapConfig.GetSuperUser()+
				","+config.LdapConfig.GetBase(), " to ",
				config.LdapConfig.GetServer(), ": ", err)
		}
		defer ld.Close()

		sreq = ldap.NewSearchRequest(
			config.LdapConfig.GetBase(), ldap.ScopeWholeSubtree,
			ldap.NeverDerefAliases, 1000, 90, false,
			"(objectClass=posixAccount)", []string{"uidNumber"},
			[]ldap.Control{})

		// Find the highest assigned UID.
		lres, err = ld.Search(sreq)
		if err != nil {
			log.Fatal("Unable to search for posix accounts in ",
				config.LdapConfig.GetBase(), ": ", err)
		}
		for _, entry = range lres.Entries {
			var uid string

			for _, uid = range entry.GetAttributeValues("uidNumber") {
				var uidNumber uint64
				uidNumber, err = strconv.ParseUint(uid, 10, 64)
				if err != nil {
					log.Print("Error parsing \"", uid, "\" as a number: ",
						err)
				} else if uidNumber > greatestUid {
					greatestUid = uidNumber
				}
			}
		}
	}

	// Connect to Cassandra so we can get a list of records to be processed.
	db, err = cassandra.NewRetryCassandraClient(
		config.DatabaseConfig.GetDatabaseServer())
	if err != nil {
		log.Fatal("Error connecting to Cassandra database at ",
			config.DatabaseConfig.GetDatabaseServer(), ": ", err)
	}

	err = db.SetKeyspace(config.DatabaseConfig.GetDatabaseName())
	if err != nil {
		log.Fatal("Error setting keyspace: ", err)
	}

	cp = cassandra.NewColumnParent()
	cp.ColumnFamily = "membership_queue"
	pred = cassandra.NewSlicePredicate()
	pred.ColumnNames = [][]byte{[]byte("pb_data")}
	kr = cassandra.NewKeyRange()
	kr.StartKey = []byte("queue:")
	kr.EndKey = []byte("queue;")

	kss, err = db.GetRangeSlices(cp, pred, kr,
		cassandra.ConsistencyLevel_QUORUM)
	if err != nil {
		log.Fatal("Error getting range slice: ", err)
	}

	for _, ks = range kss {
		mmap = make(map[string]map[string][]*cassandra.Mutation)
		var csc *cassandra.ColumnOrSuperColumn
		for _, csc = range ks.Columns {
			var col *cassandra.Column = csc.Column
			var agreement membersys.MembershipAgreement
			var m *cassandra.Mutation

			if col == nil {
				continue
			}

			if string(col.Name) != "pb_data" {
				log.Print("Column selected was not as requested: ",
					col.Name)
				continue
			}

			err = proto.Unmarshal(col.Value, &agreement)
			if err != nil {
				log.Print("Unable to parse column ", col.Name, " of ",
					ks.Key, ": ", err)
				continue
			}

			if agreement.MemberData.Username != nil {
				var attrs *ldap.AddRequest

				greatestUid++

				attrs = ldap.NewAddRequest("uid=" +
					asciiFilter(agreement.MemberData.GetUsername()) + "," +
					config.LdapConfig.GetNewUserSuffix() + "," +
					config.LdapConfig.GetBase())

				attrs.Attribute("uidNumber", []string{
					strconv.FormatUint(greatestUid, 10)})
				attrs.Attribute("gecos", []string{
					asciiFilter(agreement.MemberData.GetName())})
				attrs.Attribute("shadowLastChange", []string{"11457"})
				attrs.Attribute("shadowMax", []string{"9999"})
				attrs.Attribute("shadowWarning", []string{"7"})
				attrs.Attribute("gidNumber", []string{strconv.FormatUint(
					uint64(config.LdapConfig.GetNewUserGid()), 10)})
				attrs.Attribute("objectClass", []string{
					"account", "posixAccount", "shadowAccount", "top",
				})
				attrs.Attribute("uid", []string{
					asciiFilter(agreement.MemberData.GetUsername())})
				attrs.Attribute("cn", []string{
					asciiFilter(agreement.MemberData.GetUsername())})
				attrs.Attribute("homeDirectory", []string{"/home/" +
					asciiFilter(agreement.MemberData.GetUsername())})
				attrs.Attribute("loginShell", []string{
					config.LdapConfig.GetNewUserShell()})
				attrs.Attribute("userPassword", []string{
					agreement.MemberData.GetPwhash(),
				})

				agreement.MemberData.Id = proto.Uint64(greatestUid)
				if verbose {
					log.Print("Creating user: uid=" +
						agreement.MemberData.GetUsername() +
						"," + config.LdapConfig.GetNewUserSuffix() + "," +
						config.LdapConfig.GetBase())
				}

				if !noop {
					var group string

					err = ld.Add(attrs)
					if err != nil {
						log.Print("Unable to create LDAP Account ",
							agreement.MemberData.GetUsername(), ": ", err)
						continue
					}

					for _, group = range config.LdapConfig.GetNewUserGroup() {
						var grpadd = ldap.NewModifyRequest("cn=" + group +
							",ou=Groups," + config.LdapConfig.GetBase())

						grpadd.Add("memberUid", []string{
							agreement.MemberData.GetUsername()})

						err = ld.Modify(grpadd)
						if err != nil {
							log.Print("Unable to add user ",
								agreement.MemberData.GetUsername(),
								" to group ", group, ": ",
								err)
						}
					}
				}
			}

			col.Value, err = proto.Marshal(&agreement)
			if err != nil {
				log.Print("Error marshalling agreement: ", err)
				continue
			}

			mmap["member:"+string(agreement.MemberData.GetEmail())] =
				make(map[string][]*cassandra.Mutation)
			mmap["member:"+string(agreement.MemberData.GetEmail())][cf] =
				make([]*cassandra.Mutation, 0)

			makeMutation(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "pb_data", col.Value, now)
			makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "name", agreement.MemberData.GetName(), now)
			makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "street", agreement.MemberData.GetStreet(), now)
			makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "city", agreement.MemberData.GetCity(), now)
			makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "country", agreement.MemberData.GetCountry(), now)
			makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "email", agreement.MemberData.GetEmail(), now)
			if agreement.MemberData.Phone != nil {
				makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
					cf, "phone", agreement.MemberData.GetPhone(), now)
			}
			if agreement.MemberData.Username != nil {
				makeMutationString(mmap["member:"+string(agreement.MemberData.GetEmail())],
					cf, "username", agreement.MemberData.GetUsername(), now)
			}
			makeMutationLong(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "fee", agreement.MemberData.GetFee(), now)
			makeMutationBool(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "fee_yearly", agreement.MemberData.GetFeeYearly(), now)
			makeMutationLong(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "approval_ts", agreement.Metadata.GetApprovalTimestamp(),
				now)
			makeMutation(mmap["member:"+string(agreement.MemberData.GetEmail())],
				cf, "agreement_pdf", agreement.AgreementPdf, now)

			// Now, delete the original record.
			m = cassandra.NewMutation()
			m.Deletion = cassandra.NewDeletion()
			m.Deletion.Predicate = cassandra.NewSlicePredicate()
			m.Deletion.Predicate.ColumnNames = [][]byte{[]byte("pb_data")}
			m.Deletion.Timestamp = col.Timestamp

			mmap[string(ks.Key)] = make(map[string][]*cassandra.Mutation)
			mmap[string(ks.Key)]["membership_queue"] = []*cassandra.Mutation{m}

			// Write welcome e-mail to new member.
			if welcome != nil {
				err = welcome.SendMail(agreement.MemberData)
				if err != nil {
					log.Print("Error sending welcome e-mail to ",
						agreement.MemberData.GetEmail(), ": ", err)
				}
			}
		}

		// Apply all database mutations.
		err = db.BatchMutate(mmap, cassandra.ConsistencyLevel_QUORUM)
		if err != nil {
			log.Fatal("Error getting range slice: ", err)
		}
	}

	// Delete parting members.
	cp.ColumnFamily = "membership_dequeue"
	kr.StartKey = []byte("dequeue:")
	kr.EndKey = []byte("dequeue;")
	kss, err = db.GetRangeSlices(cp, pred, kr,
		cassandra.ConsistencyLevel_QUORUM)
	if err != nil {
		log.Fatal("Error getting range slice: ", err)
	}

	for _, ks = range kss {
		var csc *cassandra.ColumnOrSuperColumn
		mmap = make(map[string]map[string][]*cassandra.Mutation)
		for _, csc = range ks.Columns {
			var uuid cassandra.UUID
			var ldapuser string
			var col *cassandra.Column = csc.Column
			var agreement membersys.MembershipAgreement
			var attrs *ldap.ModifyRequest
			var m *cassandra.Mutation

			if col == nil {
				continue
			}

			if string(col.Name) != "pb_data" {
				log.Print("Column selected was not as requested: ",
					col.Name)
				continue
			}

			err = proto.Unmarshal(col.Value, &agreement)
			if err != nil {
				log.Print("Unable to parse column ", ks.Key, ": ", err)
				continue
			}

			if agreement.MemberData.Username == nil {
				continue
			}

			ldapuser = "******" +
				asciiFilter(agreement.MemberData.GetUsername()) + "," +
				config.LdapConfig.GetNewUserSuffix() + "," +
				config.LdapConfig.GetBase()
			if noop {
				log.Print("Would remove user ", ldapuser)
			} else {
				var groups []string
				var groups_differ bool
				var entry *ldap.Entry
				var group string

				sreq = ldap.NewSearchRequest(
					config.LdapConfig.GetBase(), ldap.ScopeWholeSubtree,
					ldap.NeverDerefAliases, 1000, 90, false,
					"(&(objectClass=posixGroup)(memberUid="+
						asciiFilter(agreement.MemberData.GetUsername())+"))",
					[]string{"cn"}, []ldap.Control{})

				lres, err = ld.Search(sreq)
				if err != nil {
					log.Print("Error searching groups for user ",
						agreement.MemberData.GetUsername(), ": ", err)
					continue
				}

				for _, entry = range lres.Entries {
					var cn string
					var found bool

					cn = entry.GetAttributeValue("cn")
					if cn == "" {
						log.Print("No group name set for ",
							agreement.MemberData.GetUsername())
						continue
					}
					groups = append(groups, cn)

					for _, group = range config.LdapConfig.GetNewUserGroup() {
						if cn == group {
							found = true
						}
					}

					for _, group = range config.LdapConfig.GetIgnoreUserGroup() {
						if cn == group {
							found = true
						}
					}

					if !found {
						groups_differ = true
					}
				}

				if groups_differ {
					log.Print("User is in other groups than expected: ",
						strings.Join(groups, ", "))

					for _, group = range config.LdapConfig.GetNewUserGroup() {
						attrs = ldap.NewModifyRequest("cn=" + group +
							",ou=Groups," + config.LdapConfig.GetBase())
						attrs.Delete("memberUid", []string{
							asciiFilter(agreement.MemberData.GetUsername())})
						err = ld.Modify(attrs)
						if err != nil {
							log.Print("Error deleting ",
								agreement.MemberData.GetUsername(), " from ",
								group, ": ", err)
						}
					}
				} else {
					var dr = ldap.NewDelRequest(ldapuser, []ldap.Control{})
					// The user appears to be only in the groups given in
					// the config.
					err = ld.Del(dr)
					if err != nil {
						log.Print("Unable to delete user ", ldapuser, ": ",
							err)
					}
				}
			}

			m = cassandra.NewMutation()
			m.Deletion = cassandra.NewDeletion()
			m.Deletion.Predicate = pred
			m.Deletion.Timestamp = col.Timestamp

			mmap[string(ks.Key)] = make(map[string][]*cassandra.Mutation)
			mmap[string(ks.Key)]["membership_dequeue"] = []*cassandra.Mutation{m}

			// 2 years retention.
			col.TTL = proto.Int32(720 * 24 * 3600)
			col.Timestamp = proto.Int64(now.UnixNano())

			uuid = cassandra.UUIDFromBytes(ks.Key[len("dequeue:"):])

			m = cassandra.NewMutation()
			m.ColumnOrSupercolumn = cassandra.NewColumnOrSuperColumn()
			m.ColumnOrSupercolumn.Column = col
			mmap["archive:"+string([]byte(uuid))] = make(map[string][]*cassandra.Mutation)
			mmap["archive:"+string([]byte(uuid))]["membership_archive"] =
				[]*cassandra.Mutation{m}
		}

		// Apply all database mutations.
		err = db.BatchMutate(mmap, cassandra.ConsistencyLevel_QUORUM)
		if err != nil {
			log.Fatal("Error getting range slice: ", err)
		}
	}

	if verbose {
		log.Print("Greatest UID: ", greatestUid)
	}
}
Exemple #4
0
func main() {
	var help bool
	var bindto, template_dir string
	var lockserv, lockboot, servicename string
	var ca, pub, priv, authserver string
	var requested_scope string
	var dbserver, keyspace string
	var searchif_tmpl *template.Template
	var permission_denied_tmpl *template.Template
	var exporter *exportedservice.ServiceExporter
	var authenticator *ancientauth.Authenticator
	var client *cassandra.RetryCassandraClient
	var ire *cassandra.InvalidRequestException
	var err error

	flag.BoolVar(&help, "help", false, "Display help")
	flag.StringVar(&bindto, "bind", "[::1]:8080",
		"The address to bind the web server to")
	flag.StringVar(&lockserv, "lockserver-uri",
		os.Getenv("DOOZER_URI"),
		"URI of a Doozer cluster to connect to")
	flag.StringVar(&ca, "cacert", "cacert.pem",
		"Path to the X.509 certificate of the certificate authority")
	flag.StringVar(&pub, "cert", "starstock.pem",
		"Path to the X.509 certificate")
	flag.StringVar(&priv, "key", "starstock.key",
		"Path to the X.509 private key file")
	flag.StringVar(&authserver, "auth-server",
		"login.ancient-solutions.com",
		"The server to send the user to")
	flag.StringVar(&template_dir, "template-dir", "",
		"Path to the directory with the HTML templates")
	flag.StringVar(&lockboot, "lockserver-boot-uri",
		os.Getenv("DOOZER_BOOT_URI"),
		"Boot URI to resolve the Doozer cluster name (if required)")
	flag.StringVar(&servicename, "service-name",
		"", "Service name to publish as to the lock server")
	flag.StringVar(&requested_scope, "scope",
		"staff", "People need to be in this scope to use the application")
	flag.StringVar(&dbserver, "cassandra-server", "localhost:9160",
		"Cassandra database server to use")
	flag.StringVar(&keyspace, "keyspace", "starstock",
		"Cassandra keyspace to use for accessing stock data")
	flag.Parse()

	if help {
		flag.Usage()
		os.Exit(1)
	}

	if len(template_dir) <= 0 {
		log.Fatal("The --template-dir flag must not be empty")
	}

	// Load and parse the HTML templates to be displayed.
	searchif_tmpl, err = template.ParseFiles(template_dir + "/search.tmpl")
	if err != nil {
		log.Fatal("Unable to parse search template: ", err)
	}

	// Load and parse the HTML templates to be displayed.
	permission_denied_tmpl, err = template.ParseFiles(template_dir +
		"/permission_denied.tmpl")
	if err != nil {
		log.Fatal("Unable to parse form template: ", err)
	}

	// Create the AncientAuth client
	authenticator, err = ancientauth.NewAuthenticator("StarStock", pub,
		priv, ca, authserver)
	if err != nil {
		log.Fatal("NewAuthenticator: ", err)
	}

	// Connect to the Cassandra server.
	client, err = cassandra.NewRetryCassandraClientTimeout(dbserver,
		10*time.Second)
	if err != nil {
		log.Fatal("Error opening connection to ", dbserver, ": ", err)
	}

	ire, err = client.SetKeyspace(keyspace)
	if ire != nil {
		log.Fatal("Error setting keyspace to ", keyspace, ": ", ire.Why)
	}
	if err != nil {
		log.Fatal("Error setting keyspace to ", keyspace, ": ", err)
	}

	// Register the URL handler to be invoked.
	http.Handle("/css/", http.FileServer(http.Dir(template_dir)))
	http.Handle("/js/", http.FileServer(http.Dir(template_dir)))
	http.Handle("/api/product", &ProductViewAPI{
		authenticator: authenticator,
		client:        client,
		scope:         requested_scope,
	})
	http.Handle("/api/edit-product", &ProductEditAPI{
		authenticator: authenticator,
		client:        client,
		scope:         requested_scope,
	})
	http.Handle("/api/products", &ProductSearchAPI{
		authenticator: authenticator,
		client:        client,
		scope:         requested_scope,
	})
	http.Handle("/", &ProductSearchForm{
		authenticator:        authenticator,
		scope:                requested_scope,
		searchifTmpl:         searchif_tmpl,
		permissionDeniedTmpl: permission_denied_tmpl,
	})

	// If a lock server was specified, attempt to use an anonymous port as
	// a Doozer exported HTTP service. Otherwise, just bind to the address
	// given in bindto, for debugging etc.
	if len(lockserv) > 0 {
		exporter, err = exportedservice.NewExporter(lockserv, lockboot)
		if err != nil {
			log.Fatal("doozer.DialUri ", lockserv, " (",
				lockboot, "): ", err)
		}

		defer exporter.UnexportPort()
		err = exporter.ListenAndServeNamedHTTP(servicename, bindto, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	} else {
		err = http.ListenAndServe(bindto, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	}
}