sobota, 27 października 2012

Podniesienie uprawnień sharepoint

Bardzo często podczas pisania programów w technologii sharepoint (np. Visual Web Part) zdarza się konieczność czasowego podniesienia uprawnień użytkownika dla wykonania konkretnej czynności. Przykładem, mogą być uprawnienia do listy. Normalny użytkownik posiada uprawnienia tylko do odczytu elementów listy (elementy listy sharpoint są podobne do rekordów w bazie danych), a co za tym idzie nie posiada uprawnień do tworzenia/modyfikowania elementów listy. Aby taki użytkownik mógł utworzyć/zmodyfikować element na takiej liście za pomocą napisanej przez nas aplikacji (np. mógł utworzyć nową 'fakturę' za pomocą aplikacji, do zarządzania fakturami, opartej na listach sharepoint), trzeba czasowo podnieść jego uprawnienia. Podniesienie uprawnień w kodzie C# w praktyce oznacza pobranie SPSite z podwyższonymi uprawnieniami (tzw. SuperSite).
Przykład:



SPSecurity.RunWithElevatedPrivileges(delegate()
{
    using (SPSite
superSite = new SPSite(SPContext.Current.Site.ID))
    {
        using (SPWeb
superWeb = superSite.OpenWeb(SPContext.Current.Web.ID))
        {
            // Your code here
        }
    }
});
 
Podczas pobierania SuperSite lepiej jest jednak wykorzystać wrapper, w postaci klasy SPSecurityHelper, zademonstrowanej przez Dana Larsona na jego tech-blogu
Klasa SPSecurityHelper:

using Microsoft.SharePoint;


    /// <summary>A class for working with elevated privilege</summary>
    public static class SPSecurityHelper
    {
        /// <summary>Returns an elevated site</summary>
        /// <param name="theSite">
        /// The site that you want an elevated instance of.
        /// You must dispose of this object unless it is part of SPContext.Current.
        /// </param>
        /// <returns>An elevated site context.</returns>
        /// <remarks>Be sure to dispose of objects created from this method.</remarks>
        public static SPSite GetElevatedSite(SPSite theSite)
        {
            var sysToken = GetSystemToken(theSite);
            return new SPSite(theSite.ID, sysToken);
        }

        /// <summary>Gets a UserToken for the system account.</summary>
        /// <param name="site"></param>
        /// <returns>A usertoken for the system account user./returns>
        /// <remarks>Use this token to impersonate the system account</remarks>
        public static SPUserToken GetSystemToken(SPSite site)
        {
            site.
CatchAccessDeniedException = false;
            try
            {
                return site.SystemAccount.UserToken;
            }
            catch (UnauthorizedAccessException)
            {
                SPUserToken sysToken = null;

                // Only use runwithelevated to grab the system user token.
                SPSecurity.RunWithElevatedPrivileges(
                    delegate()
                    {
                        using (SPSite lolcatKiller = new SPSite(site.ID))
                        {
                            sysToken = lolcatKiller.SystemAccount.UserToken;
                        }
                    }
                );
                return sysToken;
            }
        }
    }
 Wykorzystanie klasy SPSecurityHelper:

 using (SPSite superSite = SPSecurityHelper.GetElevatedSite(SPContext.Current.Site))
{
    using (SPWeb superWeb = superSite.OpenWeb(SPContext.Current.Web.ID))
    {
               // Your code here
    }
}

 Sposób wykorzystania jest bardzo prosty, tzn. do klasy SPSecurityHelper przekazujemy obiekt zwykłego SpSite, natomiast z powrotem dostajemy obiekt tego samego SpSite z podwyższonymi uprawnieniami. Dzięki wykorzystaniu klasy SPSecurityHelper kod jest krótszy oraz lepszy jakościowo.

Adres komputera, korzystającego z naszej strony

    Uzyskanie informacji nt. komputera, z którego wykonano jakąś czynność jest bardzo ważne, z punktu widzenia bezpieczeństwa. Informacja ta, jest ważna zarówno w przypadku systemów wewnętrznych (intranet), jak i systemów zewnętrznych (internet).
    W przypadku intranetu, jednym ze szczególnych przypadków jest "usuwanie rekordów z bazy danych" (np. usuwanie faktur w systemie księgowym), gdzie oprócz loginu osoby wykonującej akcję (dost. z systemu), chcemy mieć dla pewności informacje nt. komputera, z którego wykonano akcję.
    W przypadku internetu, chcemy mieć zazwyczaj informacje, nt. komputera, który próbuje się włamać do naszego systemu, lub wykonuje inną nieporządaną akcję (np. atak DDoS).

W jaki sposób uzyskać informację nt. komputera, korzystającego z naszego systemu? Wykorzystamy do tego celu klasy z przestrzeni nazw "System.Web" oraz "System.Net".

using System.Web;
using System.Net;
IPHostEntry hostEntry = Dns.GetHostEntry(HttpContext.Current.Request.ServerVariables["REMOTE_HOST"].ToString());

Praktyczny sposób wykorzystania zmiennej hostEntry prezentuje przykład, wykonany na zwykłej stronie asp.net (web forms):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web;
using System.Net;
using System.Text;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string ComputerName = "";
        IPAddress[] IpAdressList = null;
        StringBuilder sb = new StringBuilder();
        try
        {
            IPHostEntry hostEntry = Dns.GetHostEntry(HttpContext.Current.Request.ServerVariables["REMOTE_HOST"].ToString());
            ComputerName = hostEntry.HostName.ToString();
            IpAdressList = hostEntry.AddressList;
        }
        catch { }
        sb.AppendLine(string.Format("Nazwa komputera {0}", ComputerName));
        if (IpAdressList != null)
        {
            for(int i=0; i < IpAdressList.Length; i++)
            {
                sb.AppendLine(string.Format("Typ adresu IP {0}, adres IP {1}", IpAdressList[i].AddressFamily, IpAdressList[i].ToString()));
            }
        }
        string computerInfo = sb.ToString();
    }
}

   Posiadając informacje nt. komputera, możemy te informacje zapisać do bazy danych, lub do logów. W przypadku aplikacji asp.net preferuję wykorzystania gotowego rozwiązania, jakim jest log4net, natomiast w przypadku Sharepoint 2010 logowanie do natywnych logów sharepointa (jak sie to robi opisze w jednym z przyszłych wpisów).

Edit: Niestety te metody w praktyce nie okazały się na tyle dobre, jak być powinne. Nowe metody:

        public static string GetUser_IP()
        {
            string VisitorsIPAddr = string.Empty;
            if (HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != null)
            {
                VisitorsIPAddr = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
            }
            else if (HttpContext.Current.Request.UserHostAddress.Length != 0)
            {
                VisitorsIPAddr = HttpContext.Current.Request.UserHostAddress;
            }
            return VisitorsIPAddr;
        }
        public static string GetUserIP()
        {
            string ipList = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
            if (!string.IsNullOrEmpty(ipList))
            {
                return ipList.Split(',')[0];
            }
\            return HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
        }
Używanie
        string visitorIp = GetUser_IP();
        int intAddress = BitConverter.ToInt32(IPAddress.Parse(visitorIp).GetAddressBytes(), 0);

środa, 10 października 2012

Pobieranie aktualnego kursu walut z NBP

Bardzo często, podczas pisania aplikacji finansowo-księgowych zdarza nam się potrzeba przeliczania walut po ich aktualnym kursie. O ile w przypadku naprawdę dużych systemów klient najprawdopodobniej narzuci nam własne kursy (które będziemy importować np. z SAP), o tyle w przypadku pisania małych i średnich systemów przeliczanie walut po aktualnym kursie będziemy musieli wykonać samemu. Z pomocą przychodzi nam tutaj Narodowy Bank Polski, który codziennie pomiędzy godź. 10.30-12.30 wystawia plik XML z aktualnymi kursami na dany dzień. Co ważne, robi to w sposób podmiany starego pliku, tzn. że do godź. ~10.30 istnieje plik XML z wczorajszym kursem, po czym pomiędzy godzinami 10.30-12.30 zastępowany jest on nowym plikiem, o takiej samej nazwie, pod tym samym linkiem, ale z aktualnym kursem na dany dzień. Wspomniany plik XML można znaleźć pod tym linkiem.

Dzięki temu linkowi, będziemy znali aktualne kursy NBP, po których będziemy mogli dokonywać przeliczeń pomiędzy walutami po aktualnym kursie wg. NBP. Aby nieco uprościć całą sprawę, napisałem metodę, która pobiera zawartość tego pliku i udostępnia kursy w bardziej przystępnej, obiektowej formie. Mając tą metodę, możemy później napisać "timer job" (sharepoint timer job, lub aplikację konsolową uruchamianą za pomocą Windows Task Scheduler), który będzie umieszczał zawartość tego pliku w bazie danych, liście sharepointa, czy gdzie tylko chcemy.

Metoda, która pobiera aktualny kurs wygląda tak:
        private static List<Currency> ImportData(string url)
        {
            // Creates an HttpWebRequest with the specified URL.
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            // Sends the HttpWebRequest and waits for the response.           
            HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
            // Gets the stream associated with the response.
            Stream receiveStream = myHttpWebResponse.GetResponseStream();
            Encoding encode = System.Text.Encoding.GetEncoding("iso-8859-2");
            // Pipes the stream to a higher level stream reader with the required encoding format.
            StreamReader readStream = new StreamReader(receiveStream, encode);
            string readedData = readStream.ReadToEnd();
            myHttpWebResponse.Close();
            // Releases the resources of the Stream.
            readStream.Close();
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(readedData);
            XmlNodeList currencyListXml = xmlDoc.GetElementsByTagName(Constraints.POSITION);
            var date = xmlDoc.GetElementsByTagName(Constraints.PUBLICATION_DATE);
            DateTime currencyDate = Convert.ToDateTime(date[0].FirstChild.Value);

            List<Currency> currencyList = new List<Currency>();
            foreach (XmlNode currencyXml in currencyListXml)
            {
                //XmlNodeList nodes = currencyXml.ChildNodes;
                Currency currency = new Currency();
                currency.CurrencyName = currencyXml.ChildNodes[0].FirstChild.Value;
                currency.Converter = currencyXml.ChildNodes[1].FirstChild.Value;
                currency.CurrencyCode = currencyXml.ChildNodes[2].FirstChild.Value;
                currency.CurrencyRate = Convert.ToDouble(currencyXml.ChildNodes[3].FirstChild.Value);
                currency.CurrencyDate = currencyDate;
                currencyList.Add(currency);
            }
            return currencyList;
        }
 Jako parametr podajemy adres url do pliku XML z listą walut. Wykorzystano parametr, ponieważ NBP wystawia kilka, różnych plików XML z aktualnymi kursami walut (LastA.xml, LastB.xml, LastC.xml) w zależności od tego, ile i które waluty nam są potrzebne korzystamy z odpowiedniego linku (LastA.xml podany w aplikacji pobiera 37 najpopularniejszych walut).

 Sama metoda, nie jest zbyt skomplikowana. Podajemy na wejście adres url, nast. za pomocą obiektów HttpWebRequest oraz HttpWebResponse pobieramy zawartość w pliku XML jako stream. Następnie tworzymy z niego XmlDocument, z którego po kolei wydobywamy interesujące nas dane i przekazujemy je do obiektów stworzonej przez nas klasy 'Currency'. Na zakończenie metoda zwraca listę obiektów 'Currency', na której to później dużo łatwiej jest nam operować.

Przykładowy program, który pobiera aktualne kursy walut, a nast. wyświetla je na konsoli wygląda następująco:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;

namespace GetAcctualNBPCurrency
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = @"http://www.nbp.pl/kursy/xml/LastA.xml";
            List<Currency> currencyList = ImportData(url);

            foreach (Currency currency in currencyList)
            {
                Console.WriteLine("Kurs dla waluty {0} ({1}) na dzień {2} wynosi: {3}", currency.CurrencyCode, currency.CurrencyName, currency.CurrencyDate.ToShortDateString(), currency.CurrencyRate);
            }
            Console.ReadKey();
        }
        private static List<Currency> ImportData(string url)
        {
            // Creates an HttpWebRequest with the specified URL.
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            // Sends the HttpWebRequest and waits for the response.           
            HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
            // Gets the stream associated with the response.
            Stream receiveStream = myHttpWebResponse.GetResponseStream();
            Encoding encode = System.Text.Encoding.GetEncoding("iso-8859-2");
            // Pipes the stream to a higher level stream reader with the required encoding format.
            StreamReader readStream = new StreamReader(receiveStream, encode);
            string readedData = readStream.ReadToEnd();
            myHttpWebResponse.Close();
            // Releases the resources of the Stream.
            readStream.Close();
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(readedData);
            XmlNodeList currencyListXml = xmlDoc.GetElementsByTagName(Constraints.POSITION);
            var date = xmlDoc.GetElementsByTagName(Constraints.PUBLICATION_DATE);
            DateTime currencyDate = Convert.ToDateTime(date[0].FirstChild.Value);

            List<Currency> currencyList = new List<Currency>();
            foreach (XmlNode currencyXml in currencyListXml)
            {
                //XmlNodeList nodes = currencyXml.ChildNodes;
                Currency currency = new Currency();
                currency.CurrencyName = currencyXml.ChildNodes[0].FirstChild.Value;
                currency.Converter = currencyXml.ChildNodes[1].FirstChild.Value;
                currency.CurrencyCode = currencyXml.ChildNodes[2].FirstChild.Value;
                currency.CurrencyRate = Convert.ToDouble(currencyXml.ChildNodes[3].FirstChild.Value);
                currency.CurrencyDate = currencyDate;
                currencyList.Add(currency);
            }
            return currencyList;
        }
    }
    class Currency
    {
        public string CurrencyName { get; set; }
        public string Converter { get; set; }
        public string CurrencyCode { get; set; }
        public double CurrencyRate { get; set; }
        public DateTime CurrencyDate { get; set; }

        public Currency() { }
    }
    class Constraints
    {
        public static readonly string PUBLICATION_DATE = "data_publikacji";
        public static readonly string POSITION = "pozycja";
        public static readonly string CURRENCY_NAME = "nazwa_waluty";
        public static readonly string CONVERETER = "przelicznik";
        public static readonly string CURRENCY_CODE = "kod_waluty";
        public static readonly string CURRENCY_RATE = "kurs_sredni";
    }
}
P.S. Zasady korzystania z danych publikowanych na stronie NBP można znaleźć w statusie serwisu pod tym adresem.